https://vjudge.net/problem/HDU-2473
题意:
有一堆垃圾邮件需要识别。一开始每封邮件是互相不关联的。给出两种操作,第一种是指出两封邮件具有相同的特征,即两封邮件关联,且这种关系是传递的。第二种是指出某封邮件被误判,要求它断绝与其他所有邮件的关系,最后问一共有多少种互相不关联的邮件。
思路:
这种题一开始就看出来要用并查集,但是涉及到了删除操作,就比较困难,解决并查集单个节点的操作,有一种方法,叫做设立虚父马甲,这是从这题学到的。
首先,看第一种方法,直接把孤立的节点设为自己。例如,有5封邮件,2,3,4,5的根均为1,当1被孤立的时候,par[1] = 1。2,3,4,5向上查询,查到的根还是为1,这种方法不可行。
再来看第二种方法,把孤立的节点的父亲随便设置为一个不存在的值。例如1,2,3,4,5的根均为4,那么当4被孤立的时候,设置par[4] = 7,然后其他点向上查询,则都是查到的7,还是没有达到孤立的效果,这个方法也是不可行的。
接下来看看设置虚父马甲的方法。这个方法是在初始化的时候把每一个节点i的父亲par[i] 设置为 i+n,然后把2 * n 到 2 * n + m的父亲设置为自己,为什么是2 * n + m呢?因为一共有m种操作,如果所有的操作都是孤立,那么就可能用到m个额外的虚父节点。举一个例子,有0,1,2,3,4,5一共6封信,每封信的初始化后父亲分别是6,7,8,9,10,11,现在假设是以2为根,那么实际情况就是0,1,2,3,4,5的父亲节点都是8,现在,将2孤立,现在让par[2] = id++,(id是从2*n开始的,他代表额外的新的未使用过的父亲节点),par[2] = 12,但是其他的点的父亲还是8,所以就达到了可以孤立点的目的,这是一种极为巧妙的方法。具体还是详见代码
代码:
1 #include <stdio.h> 2 #include <string.h> 3 4 const int N = 100005; 5 const int M = 1000005; 6 7 int par[N+N+M]; 8 bool vis[N+N+M]; 9 10 int fin(int x) 11 { 12 if (x == par[x]) return x; 13 else return par[x] = fin(par[x]); 14 } 15 16 void unit(int x,int y) 17 { 18 x = fin(x); 19 y = fin(y); 20 21 if (x != y) par[x] = y; 22 } 23 24 int main() 25 { 26 int n,m; 27 28 int cas = 0; 29 30 while (scanf("%d%d",&n,&m) != EOF) 31 { 32 if (m == 0 && n == 0) break; 33 34 printf("Case #%d: ",++cas); 35 36 memset(vis,0,sizeof(vis)); 37 38 for (int i = 0;i < n;i++) 39 par[i] = i + n; 40 41 for (int i = n;i < n + n + m;i++) 42 par[i] = i; 43 44 int id = n + n; 45 46 for (int i = 0;i < m;i++) 47 { 48 char op; 49 int x,y; 50 51 scanf(" %c%d",&op,&x); 52 53 if (op == 'M') 54 { 55 scanf("%d",&y); 56 57 unit(x,y); 58 } 59 else 60 { 61 par[x] = id++; 62 } 63 } 64 65 int ans = 0; 66 67 for (int i = 0;i < n;i++) 68 { 69 if (!vis[fin(i)]) 70 { 71 ans++; 72 vis[fin(i)] = 1; 73 } 74 } 75 76 printf("%d ",ans); 77 } 78 79 return 0; 80 }