HDU 6370(并查集)
传送门
题面:
Werewolf
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 559 Accepted Submission(s): 123
Problem Description
"The Werewolves" is a popular card game among young people.In the basic game, there are 2 different groups: the werewolves and the villagers.
Each player will debate a player they think is a werewolf or not.
Their words are like "Player x is a werewolf." or "Player x is a villager.".
What we know is :
1. Villager won't lie.
2. Werewolf may lie.
Of cause we only consider those situations which obey the two rules above.
It is guaranteed that input data exist at least one situation which obey the two rules above.
Now we can judge every player into 3 types :
1. A player which can only be villager among all situations,
2. A player which can only be werewolf among all situations.
3. A player which can be villager among some situations, while can be werewolf in others situations.
You just need to print out the number of type-1 players and the number of type-2 players.
No player will talk about himself.
Input
The first line of the input gives the number of test cases T.Then T test cases follow.
The first line of each test case contains an integer N,indicating the number of players.
Then follows N lines,i-th line contains an integer x and a string S,indicating the i-th players tell you,"Player x is a S."
limits:
1≤T≤10
1≤N≤100,000
1≤x≤N
S∈ {"villager"."werewolf"}
Output
For each test case,print the number of type-1 players and the number of type-2 players in one line, separated by white space.
Sample Input
1 2 2 werewolf 1 werewolf
Sample Output
0 0
Source
2018 Multi-University Training Contest 6
题目描述:
有n个人在玩狼人杀,每个人有两种身份,狼或者村民,村民必须要将真话,而狼可能讲假话。现在每个人都会说出一个除了自己外的一个人身份,现在问你有多少个人确定是村民,有多少个人确定是狼人。
题目分析:
因为题目不排斥所有玩家都是狼人的情况(这一点都不符合游戏规则好吧),因此题目中所有的人说的所有的话均有可能是假的。因此我们完全无法确定是否有人是村民。但是存在一定是狼人的情况。
当出现一种类似下图的情况:
当我们发现有连续个点是有村民的边,如点1,点2,点3,点4,点5,点6;而这些个连续的其中一个点(如图中的点6)有一条狼人边连到了这些个连续的其他的点(如上图练到了点1)。
此时,我们用反证法可以证明,倘若1号点是村民,则根据村民不会说谎的性质可以判断出1到6号点全是村民,而根据村民不会说谎的性质,只能证明出1号点必为狼人。此时我们同时也可以发现,倘若1号点是狼人,则根据狼人会说谎的性质可知,指向1号点的村民边则必定是狼人。
因此我们的算法雏形就初步显现出来了。
因为我们要维护一些连续的村民点,因此我们可以用一个并查集进行维护。我们可以将村民边上的两个点不断的用并查集去合并,而当我们遍历狼边的时候,倘若我们发现狼边上的两个点都在一个集合中,则说明必定满足上述的情况,则我们不断遍历这条狼人边所指向的那个结点(如上图的1号点),判断有多少条指向它的村民边即可。(此处我们可以将村民边反向建立,这样可以让我们高效的查询)。
代码:
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef pair<int,int>PLL;
vector<int>human[maxn];
vector<PLL>wolf;
int Far[maxn];
int n;
void init(){//初始化
for(int i=1;i<=n;i++){
Far[i]=i;
}
wolf.clear();
for(int i=0;i<=n;i++){
human[i].clear();
}
}
int Find_F(int x){//并查集找爸爸
if(Far[x]==x) return x;
else return Far[x]=Find_F(Far[x]);
}
void unit(int x,int y){//并查集合并
x=Find_F(x);
y=Find_F(y);
if(x==y) return ;
else Far[x]=y;
}
bool same(int x,int y){//判断是否在一个集合
return Find_F(x)==Find_F(y);
}
int ans=0;
void dfs(int x){//dfs找指向该点的村民边
int len=human[x].size();
for(int i=0;i<len;i++){
ans++;
dfs(human[x][i]);
}
}
int main()
{
int t;
cin>>t;
while(t--){
scanf("%d",&n);
init();
for(int i=1;i<=n;i++){
char str[20];
int op;
scanf("%d%s",&op,str);
if(str[0]=='v'){
human[op].push_back(i);//反向建边
unit(op,i);
}
else{
wolf.push_back(make_pair(i,op));
}
}
ans=0;
int sz=wolf.size();
for(int i=0;i<sz;i++){
int from=wolf[i].first;
int to=wolf[i].second;
if(same(from,to)){
ans++;
dfs(to);
}
}
cout<<"0 "<<ans<<endl;
}
}