题目链接:http://poj.org/problem?id=1703
题意:有N个人,分别属于两个帮派。以两种形式给你M个信息①A a b 问a和b是否属于同一个帮派 ②D a b 告诉你a和b属于不同的帮派。(题中”每个帮派至少有一个人“这句话可无视,因为根据题目的输入形式,再考虑这句话的话这题实际上是无法做的)
分析:集合问题,并查集经典。
这里主要是解决集合的合并问题。因为每次给出的信息是两个人属于不同的集合,与一般给出的属于同一个集合的不同。那么我们也可以将他们合并,前提是要记录他们之间的关系。这里另开一个数组relation[],记录一个节点与其父亲节点的关系。relation[x]=0代表该节点与父亲节点属于同一帮派,relation[x]=1则代表他们不属于同一帮派。这样就可以处理并记录两个节点的信息。但问题又来了,当对两个节点x,y进行Union操作的时候,节点间的关系有如何处理呢?首先节点x,y的节点关系(relation[x],relation[y])并不需要变化,因为它们的父亲节没变。要变的只是x和y的父亲节点的relation。暂且把x,y的父亲节点叫做fx和fy吧。如果直接将x的父亲节点连到y的父亲节点,即father[fy]=father[fx],那么relation[y]应当怎么变化呢?这里可以列出x,y所有情况仔细分析一下:
由以上可得 relation[fy]=(relation[x]+relation[y])^1。
但是当进行find操作时进行路径压缩时可能会改变某个节点的父亲节点,这样relation又要变化了。这种情况可以根据当前节点x的父亲节点fx与fx的父亲节点fxx的关系推出x与fxx的关系(推理同上,就是列出所有可能情况总结一下啦):relation[x]=(relation[x]+relation[fx])%2。
怎么样,不难吧?那再试试这题?http://poj.org/problem?id=1182(这题的详细分析见这里:http://blog.csdn.net/hrhacmer/article/details/9408571)
Code:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
#include <map>
#include <set>
#define eps 1e-8
#define LL long long
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(a))
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=100005;
int father[maxn],rela[maxn];
char s[5];
int n,m;
void make_set(){
for(int i=1;i<=n;i++){
father[i]=i;
rela[i]=0;
}
}
int find_set(int x){
if(x!=father[x]){
int tmp=father[x];
father[x]=find_set(father[x]);
rela[x]=(rela[x]+rela[tmp])%2;
}
return father[x];
}
void Union(int a,int b){
int x=find_set(a);
int y=find_set(b);
if(x!=y) {
father[y]=x;
rela[y]=(rela[a]+rela[b])^1;
}
}
int main()
{
int T,a,b;
scanf("%d",&T);
while(T--){
scanf("%d %d",&n,&m);
make_set();
while(m--){
scanf("%s %d %d",s,&a,&b);
if(s[0]=='A'){
int fa=find_set(a);
int fb=find_set(b);
if(fa==fb){
if(rela[a]==rela[b]) printf("In the same gang.\n");
else printf("In different gangs.\n");
}
else printf("Not sure yet.\n");
}
else Union(a,b);
}
}
return 0;
}