POJ 1703 Find them, Catch them【种类/带权并查集+判断两元素是否在同一集合/不同集合/无法确定+类似食物链】
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.
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.
【题意】:一共有俩犯罪团伙,N个人中有人可能是罪犯,D a b表示a和b属于同一个犯罪团伙,A a b表示询问a和b的团伙关系。(else:CSU-1904-精灵的交际网)
【分析】:
并查集的拓展:
并查集最开始的使用是用于判断一个图是否是连通图,由于并查集查询特点的特点(查询复杂度为O(1))所以用得很广。
并查集的变形一般是和向量偏移(类别偏移)一起结合 。
法一:
***用了两个并查集,把每个罪犯复制出两个来。一个是原来的,一个对称后的,以判断a b的相对团伙。
同一个集合表示可以有一个罪犯所述团伙推出其他罪犯所属团伙,用于判断是否能确定。(数据不上50万都懒得用Rank按秩合并。
***定义并查集为:并查集里的元素i-x表示i属于帮派x,同一个并查集的元素同时成立,可见所有元素个数为2 * N,如果i表示属于帮派A,那么i + N表示属于帮派B,
每次输入两个家伙不在同一帮派的时候,就合并他们分属两个帮派的元素。我认为这是最简单最好懂的算法,
那些利用复杂节点带权重接着依靠大量if-else维护并查集的做法都不够美。
这道题目其实归根结底就是保留了所有可能性,我们只知道x和y不属于同一集合,但我们不能确定究竟x属于集合A还是集合B,于是我们保留所有可能性,对x-A和x-B都做了一次记录。
***因为有两个帮派,因此对于每个人只要创建 2 个元素 i - A,i - B,并利用 2*N 个元素建立并查集。
假设 x , y属于不同的帮派,x , y + N 则是同一个帮派,x + N , y 同理。因此只需要将(x , y + N) 和 (x + N , y) 合并即可
#include<cstdio> #include<cstring> using namespace std; #define N 200010 int t,n,m,a,b,fa[N]; int root(int x){ return fa[x]==x?x:fa[x]=root(fa[x]); } inline bool alk(int x,int y){ return root(x)==root(y); } inline void unite(int x,int y){ x=root(x); y=root(y); if(x!=y) fa[x]=y; } int main(){ scanf("%d",&t); char s[5]; while(t--){ //memset(fa,0,sizeof(fa)); scanf("%d%d%",&n,&m); for(int i=1;i<=2*n;i++) fa[i]=i; while(m--){ scanf("%s%d%d",s,&a,&b); if(s[0]=='D'){ unite(a,b+n); unite(a+n,b); } else { if(root(a)==root(b)) { printf("In the same gang.\n"); } else if(root(a)==root(b+n)) { printf("In different gangs.\n"); } else { printf("Not sure yet.\n"); } } } } } /* 375ms 988kB */
【注意初始化2*n】
法二:(用scanf,cin要超时)因为ans的值只能为0和1(只有两个帮派),所以类别偏移可以用位运算.(http://www.cnblogs.com/zzy19961112/p/6043420.html)
带权并查集,利用r[ ]数组记录每个元素与其父亲节点的关系。 r[ x ] = 0 代表 x 与其父亲节点是同一个帮派的; r[ x ] = 1 代表 x 与其父亲节点是敌对帮派的; 一开始每个人都是自己的父亲节点 f[ x ] = x,每个人与自己的关系都是同属于一个阵营 r[ x ] = 0; 1、find( ) 函数寻找根节点的时候要不断更新 r[ ]数组 根据子节点与父节点的关系和父节点和爷爷节点的关系推到子节点和爷爷节点的关系。 很容易通过穷举发现其关系式:a 和 b 的关系为 r1, b 和 c 的关系为r2,则 a 和 c 的关系为: r3 = ( r1 + r2) % 2;
(爷爷,父亲) | (父亲,儿子) | (爷爷,儿子) |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
2、 Union的时候更新两棵树的关系
定义:fx 为 x的根节点, fy 为 y 的根节点,联合时,使得fa[ fy ] = fx;同时也要寻找 fx 和 fy 的关系,其关系为(r[ x ] + 1 - r[ y ]) % 2;
因为确定了 x 和 y 的关系是 1 ,因此 r[ fy ] = (r[ x ] + 1 - r[ y ]) % 2;
***********************************************************************************************http://blog.csdn.net/freezhanacmore/article/details/8774033
除了像普通的并查集定义一个 p[] 记录父亲节点外,还定义一个 r[] 记录当前点与其所属的连通分量的根节点的关系。
r[] = 0 表示属于同一个帮派; r[] = 1表示与其根节点属于不同的帮派。
开始时初始化自己是自己的父亲 p[x] = x,自己与自己属于同一类 r[x] = 0.
一旦输入 D 断定 x 和 y 属于不同集合后,就连接 x 和 y 所在的树,同时更新 r[]
一旦输入 A
如果 find(x) 不等于 find(y) 说明还没有判断过 x 与 y 直接输出关系不确定即可
位运算版带权并查集
取余版带权并查集
Not sure yet.
如果find(x)等于 find(y) ,但是他们的r不等,说明属于不同帮派,输出In different gangs.
如果他们的r相等,说明属于同一个帮派,则输出In the same gang
注意:1.find()函数寻找根节点的时候要不断的更新 r
根据子节点与父亲节点的关系和父节点与爷爷节点的关系,推导子节点与爷爷节点的关系
如果 a 和 b 的关系是 r1, b 和 c 的关系是 r2,
那么 a 和 c 的关系就是 (r1+r2)%2 . PS:因为只用两种情况所以对 2 取模。
如果实在不好理解,那么我们就枚举推理一下,共有 2*2 = 4种情况:
(a, b) (b, c) (a, c) (r1+r2)%2
0 0 0 0 a 和 b是同类 , b 和 c 是同类, 所以 a 和 c 也是同类
0 1 1 1 a 和 b是同类 , b 和 c 是异类, 所以 a 和 c 也是异类
1 0 1 1 a 和 b是异类 , b 和 c 是同类, 所以 a 和 c 是异类
1 1 0 0 a 和 b是异类 , b 和 c 是异类, 所以 a 和 c 是同类
2.Union()联合两棵树的时候也要更新两棵树的根的关系
定义:fx 为 x的根节点, fy 为 y 的根节点
联合时,使得 p[fx] = fy; 同时也要寻找 fx 与 fy 的关系。关系为:(r[x]+r[y]+1)%2
如何证明?
fx 与 x 的关系是 r[x],
x 与 y 的关系是 1 (因为确定是不同类,才联合的),
y 与 fy 关系是 r[y],模 2 是因为只有两种关系
所以又上面的一点所推出的定理可以证明 fx 与 fy 的关系是: (r[x]+r[y]+1)%2
#include <stdio.h> #define MAXV 100010 int fa[MAXV],r[MAXV]; int find(int x){ int rt; if(r[x]!=x){ rt=find(r[x]); fa[x]=fa[x]^fa[r[x]];//类别偏移 return r[x]=rt; } return x; } void join(int x,int y){ int fx,fy; fx=find(x); fy=find(y); r[fx]=fy; fa[fx]=~(fa[y]^fa[x]);//类别偏移 } int main(){ int i,n,m,a,b; char c; int t; scanf("%d",&t); while(t--){ scanf("%d%d\n",&n,&m); for(i=0;i<=n;i++){ fa[i]=0; r[i]=i; } for(i=1;i<=m;i++){ scanf("%c %d %d\n",&c,&a,&b); if(c=='D'){ join(a,b); }else{ if(n==2) //特殊解 printf("In different gangs.\n"); else if(find(a)==find(b)) { if(fa[a]==fa[b]) printf("In the same gang.\n"); else printf("In different gangs.\n"); } else printf("Not sure yet.\n"); } } } return 0; } /* 344ms 988kB */
#include<cstdio> const int maxn = 100000+10; int p[maxn]; //存父亲节点 int r[maxn]; //存与根节点的关系,0 代表同类, 1代表不同类 int find(int x) //找根节点 { if(x == p[x]) return x; int t = p[x]; //记录父亲节点 方便下面更新r[] p[x] = find(p[x]); r[x] = (r[x]+r[t])%2; //根据子节点与父亲节点的关系和父节点与爷爷节点的关系,推导子节点与爷爷节点的关系 return p[x]; //容易忘记 } void Union(int x, int y) { int fx = find(x); //x所在集合的根节点 int fy = find(y); p[fx] = fy; //合并 r[fx] = (r[x]+1+r[y])%2; //fx与x关系 + x与y的关系 + y与fy的关系 = fx与fy的关系 } void set(int n) { for(int x = 1; x <= n; x++) { p[x] = x; //自己是自己的父节点 r[x] = 0; //自己和自己属于同一类 } } int main() { int T; int n, m; scanf("%d", &T); while(T--) { scanf("%d%d%*c", &n, &m); set(n); char c; int x, y; while(m--) { scanf("%c%d%d%*c", &c, &x, &y); //注意输入 //printf("%c\n", c); if(c == 'A') { if(find(x) == find(y)) //如果根节点相同,则表示能判断关系 { if(r[x] != r[y]) printf("In different gangs.\n"); else printf("In the same gang.\n"); } else printf("Not sure yet.\n"); } else if(c == 'D') { Union(x, y); } } } return 0; }