POJ-1703-Find them, Catch them(并查集分类)
Description
The police office in Tadu City decides to say ends to the chaos, as launch actions to root up the TWO gangs in the city, Gang Dragon and Gang Snake. However, the police first needs to identify which gang a criminal belongs to. The present question is, given two criminals; do they belong to a same clan? You must give your judgment based on incomplete information. (Since the gangsters are always acting secretly.)
Assume N (N <= 10^5) criminals are currently in Tadu City, numbered from 1 to N. And of course, at least one of them belongs to Gang Dragon, and the same for Gang Snake. You will be given M (M <= 10^5) messages in sequence, which are in the following two kinds:
1. D [a] [b]
where [a] and [b] are the numbers of two criminals, and they belong to different gangs.
2. A [a] [b]
where [a] and [b] are the numbers of two criminals. This requires you to decide whether a and b belong to a same gang.
Input
The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. Each test case begins with a line with two integers N and M, followed by M lines each containing one message as described above.
Output
For each message "A [a] [b]" in each case, your program should give the judgment based on the information got before. The answers might be one of "In the same gang.", "In different gangs." and "Not sure yet."
Sample Input
1
5 5
A 1 2
D 1 2
A 1 2
D 2 4
A 1 4
Sample Output
Not sure yet.
In different gangs.
In the same gang.
这个题值得好好做一下。有两种思路。
思路一:建立虚拟节点。
a表示帮派A,a+N表示帮派B。D[a][b]时,a和b+N合并,b和a+N合并。如果a和b或a+N和b+N为一个帮派则a和b是一个帮派,如果a和b+N或b和a+N为一个帮派则,a和b不是一个帮派。
思路二:带权并查集。
建一个权数组,表示与父节点的关系(0同1异)。
find():
每次更新pre[x]时也随着更新权r[x]。
举个例子,新节点并入星状集有三种情况:
1.某节点P0并入根节点r[]=0,那么r[P0]=1,
2.某节点P1并入一个r[]==1的节点(非根),那么r[P1]=0;
3.某节点P2并入一个r[]==0的节点(非根),那么r[P2]=1;
注意:这个不是真正的模型,只是说明这三种情况merge()之后的权。实际模型在压缩完成后权值才真正定型。也就说merge()完后该图应该还是一个星状结构,P1、P2父节点为根节点。
当然,由于根节点并入其他节点等许多复杂情况,并查集结构不可能一直是星状的。但原理是一样的。它和它的爷爷节点的同异,要看它的父节点和它本身的权值。
新的权更新公式:
r[x]=(r[x]+r[t])%2; //这是更新之后的新权值(和爷爷(新父亲)的同异)。
merge():
两个树合并根节点更新;两个根节点的关系:
r[fx]=(r[x]+r[y]+1)%2;
最后find()之后就成星状集了,只需要看和根节点是否是同一帮派就OK了,也就是权为0是一类权为1是一类。
虚拟节点:
#include<cstdio> using namespace std; int pre[200010]; int N,M; int find(int x){ while(pre[x]!=x){ int r=pre[x]; pre[x]=pre[r]; x=r; } return x; } void merge(int x,int y){ int fx=find(x); int fy=find(y); if(fx!=fy) pre[fx]=fy; } int same(int x,int y){ return find(x)==find(y); } int main(){ int T,a,b; char c[3]; scanf("%d",&T); while(T--){ scanf("%d%d",&N,&M); for(int i=1;i<=N*2;i++) pre[i]=i; while(M--){ scanf("%s%d%d",c,&a,&b); if(c[0]=='D'){ merge(a,b+N); merge(a+N,b); } else{ if(same(a,b)||same(a+N,b+N)) printf("In the same gang.\n"); else if(same(a,b+N)||same(a+N,b)) printf("In different gangs.\n"); else printf("Not sure yet.\n"); } } } return 0; }
带权并查集:
#include<cstdio> using namespace std; int N,M; int pre[100005]; int r[100005];//权数组,代表与父节点的关系 0同 1异 int find(int x){ if(x==pre[x]) return x; int t=pre[x];// 拿到x的父亲节点 pre[x]=find(pre[x]); r[x]=(r[x]+r[t])%2; // 或 r[x]=r[x]^r[t]; return pre[x]; } void merge(int x,int y){ int fx=find(x); int fy=find(y); if(fx!=fy) pre[fx]=fy; r[fx]=(r[x]+r[y]+1)%2; // 或 r[fx]=~(r[x]^r[y]); 根节点 } int main(){ int T,a,b; char c[5]; scanf("%d",&T); while(T--){ scanf("%d%d",&N,&M); for(int i=1;i<=N;i++){ pre[i]=i; r[i]=0; } while(M--){ scanf("%s%d%d",c,&a,&b); if(c[0]=='D'){ merge(a,b); } else { if(find(a)==find(b)){ if(r[a]!=r[b]) printf("In different gangs.\n"); else printf("In the same gang.\n"); } else printf("Not sure yet.\n"); } } } return 0; }