题目链接: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; }