一、鄙人的浅薄认知:给你一些具有父子关系的数据,你的任务就是把他们家家谱汇总出来,然后,顺便记录一些年龄啊,几个孩子什么的,最后回答问题就行了。
二、典型例题
1、HDU 1232 畅通工程
http://acm.hdu.edu.cn/showproblem.php?pid=1232
这道题是大多数人的入门题。
题解:有好多村子,现在已经建了一些路了,问还要建多少才能让所有的村子相通。
首先至少ans = n - 1条,如果目前互不认识。然后建立父子关系,不是一个祖宗的给他们建了路了,父子关系成立,他们成为一家子了,ans 自然 - 1。
是一个祖宗的本来就是一家子,不用管。然后一直这样判断直到读入结束。
代码:
1 #include<iostream> 2 #include<cstring> 3 #include<queue> 4 using namespace std; 5 const int N = 1010; 6 int father[N]; 7 8 int Find(int x) { 9 if (father[x] == x) return x; 10 return father[x] = Find(father[x]); 11 } 12 13 int main() 14 { 15 int n, m; 16 while (scanf("%d", &n) && n) { 17 scanf("%d", &m); 18 for (int i = 0; i <= n; i++) { 19 father[i] = i;//刚开始自己是自己的父亲 20 } 21 int a, b; 22 int ans = n - 1; 23 for (int i = 0; i < m; i++) { 24 scanf("%d %d", &a, &b); 25 int f1 = Find(a); 26 int f2 = Find(b); 27 if (f1 != f2) { 28 father[f1] = f2; 29 ans--; 30 } 31 } 32 printf("%d ", ans); 33 } 34 return 0; 35 }
2、HDU 3635 Dragon Balls
http://acm.hdu.edu.cn/showproblem.php?pid=3635
题解:也是简单的并查集,就是比畅通工程多用了一个数组。
孙悟空搞事情,见着美女 T 就把 A 城市的龙珠运到 B 城市,见着美人 Q 就问你 A 龙珠在哪个城市 X ,还有 X 城市有几个龙珠,还有 A 龙珠被糟蹋了几次(运了几回)。
多用一个num[N]记录每个爸爸有几个儿子,用step[N]记录每个龙珠被糟蹋的次数。
代码:
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 #include<string> 5 #include<cmath> 6 #include<map> 7 using namespace std; 8 //num记录父节点下的子节点个数 step记录步数 9 int pre[200005],num[200005],step[200005]; 10 11 int find(int x){ 12 if(pre[x]!=x){ 13 int t=pre[x];//改变根节点前用t保存pre[x] 14 pre[x]=find(pre[x]);//更新根节点 ,直到找到父节点 15 step[x]+=step[t];//将下级节点的步数加到他的上级节点步数中 16 } 17 return pre[x]; 18 } 19 20 int main() 21 { 22 int T; 23 cin>>T; 24 int n,m,nn=1; 25 int f1,f2; 26 while(T--){ 27 printf("Case %d: ",nn++);//注意位置,一共有T个case 28 scanf("%d %d",&n,&m); 29 for(int i=1;i<=n;i++){ 30 pre[i]=i;//每个点相互独立,上级都是自己 31 num[i]=1; 32 step[i]=0; 33 } 34 char c; 35 int a,b,d; 36 while(m--){ 37 getchar(); 38 scanf("%c",&c); 39 if(c=='T'){ 40 scanf("%d %d",&a,&b); 41 f1=find(a); 42 f2=find(b); 43 if(f1!=f2){ 44 pre[f1]=f2; 45 step[f1]++;//a的所有龙珠移到了b,步数+1 46 num[f2]+=num[f1];//f1的所有龙珠传给上级f2 47 num[f1]=0;//f1中龙珠数归0 48 } 49 } 50 else if(c=='Q'){ 51 scanf("%d",&d); 52 int x=find(d); 53 printf("%d %d %d ",x,num[x],step[d]); 54 } 55 } 56 } 57 return 0; 58 }
3、种类并查集
在前面简单并查集的基础上增加一个vis[i] ,记录 i 和同一集合的他的祖先的关系。
典型例题:POJ 1703 Find them,Catch them
http://poj.org/problem?id=1703
题解:给你一些数据 D a b 表示他们不是同伙,A a b 表示询问a 和 b之间的关系:同伙,不是同伙,不知道。
代码:
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 #define ll long long 8 using namespace std; 9 10 const int N = 100005; 11 int father[N]; 12 bool vis[N];//记录性别,同性(同伙)为true,异性(不同伙)为false 13 14 int Find(int x, bool &sex)//返回父亲和父亲性别 15 { 16 sex = true; 17 int r = x; 18 while(x != father[x]) 19 { 20 if(vis[x] == false) 21 sex = !sex; 22 x = father[x]; 23 } 24 father[r] = x;//状态压缩 25 vis[r] = sex;// 26 return x; 27 } 28 29 int main() 30 { 31 int T; 32 scanf("%d", &T); 33 while(T--) 34 { 35 for(int i = 0; i < N; i++) 36 { 37 father[i] = i; 38 vis[i] = false; 39 } 40 41 int n, m; 42 scanf("%d %d", &n, &m); 43 getchar(); 44 while(m--) 45 { 46 char c; 47 int x, y; 48 scanf("%c %d %d", &c, &x, &y); 49 getchar(); 50 bool flag1 = false, flag2 = false;//这两父亲的性别 51 int f1 = Find(x, flag1); 52 int f2 = Find(y, flag2); 53 if(c == 'D') 54 { 55 father[f1] = f2;//将两个集合合并 56 vis[f1] = flag1 ^ flag2;//同为同性或同为异性都是一伙儿的 57 } 58 else 59 { 60 if(f1 == f2)//都在集合里就能判断是否一伙 61 { 62 if(flag1 == flag2) printf("In the same gang. "); 63 else printf("In different gangs. "); 64 } 65 else//有一个不在集合里就莫得办法了 66 printf("Not sure yet. "); 67 } 68 } 69 } 70 71 return 0; 72 }
典型例题:HDU 1829 A Bug's Life
http://acm.hdu.edu.cn/showproblem.php?pid=1829
题解:给你一些数据,表示两人是情侣关系。1和2,2和3分别是情侣关系(不用管2踏了几只船),假如再来个关系1和3,哎呀不用怀疑,这俩肯定是好基友。
题目就是在询问你能不能发现好基友。
本题和上面的 POJ 1703 很像,代码只是做了稍稍改变。
代码:
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 #define ll long long 8 using namespace std; 9 10 const int N = 1000005; 11 int father[N]; 12 bool vis[N];//记录性别,同性(同伙)为true,异性(不同伙)为false 13 14 int Find(int x, bool& sex)//返回父亲和父亲性别 15 { 16 sex = true; 17 int r = x; 18 while (x != father[x]) 19 { 20 if (vis[x] == false) 21 sex = !sex; 22 x = father[x]; 23 } 24 father[r] = x;//状态压缩 25 vis[r] = sex;// 26 return x; 27 } 28 29 int main() 30 { 31 int T; 32 scanf("%d", &T); 33 int t = 1; 34 while (T--) 35 { 36 for (int i = 0; i < N; i++) 37 { 38 father[i] = i; 39 vis[i] = false; 40 } 41 42 int n, m; 43 scanf("%d %d", &n, &m); 44 45 bool flag = true; 46 while (m--) 47 { 48 int x, y; 49 scanf("%d %d", &x, &y); 50 51 if(flag) 52 { 53 bool flag1 = false, flag2 = false;//这两父亲的性别 54 int f1 = Find(x, flag1); 55 int f2 = Find(y, flag2); 56 if (f1 == f2)//都在集合里就能判断是否一伙 57 { 58 if (flag1 == flag2) flag = false; 59 } 60 else 61 { 62 father[f1] = f2; 63 vis[f1] = flag1 ^ flag2; 64 } 65 } 66 } 67 68 printf("Scenario #%d: ", t++); 69 if (flag) 70 printf("No suspicious bugs found! "); 71 else 72 printf("Suspicious bugs found! "); 73 74 } 75 76 return 0; 77 }
4、带权并查集
就是比简单并查集多了一个数组d[N],用来记录当前节点到根节点的有向距离。
因此在Find函数里,还要注意逐层累加。实现的代码可参考下面例题的代码。
经典例题:HDU 2818 Building-Block
http://acm.hdu.edu.cn/showproblem.php?pid=2818
题解:有n个木块,开始分成n堆。读入M a b 就将 a 所在堆的木块放在 b 堆的上面,在同一堆的话就不用管了。读入C a 就输出此时木块 a 下面的木块的个数。
代码:
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 #define ll long long 8 using namespace std; 9 10 const int N = 30005; 11 int father[N];//她老板 12 int num[N];//所在公司员工总数 13 int d[N];//在这个集合里的地位(数字越大,地位越低,位置越靠后) 14 15 int Find(int x) 16 { 17 if (x == father[x]) return x; 18 else 19 { 20 int t = father[x]; 21 father[x] = Find(father[x]); 22 if(t != father[t])//目前的老板不是终极大BOSS 23 d[x] += d[t] - 1;//他的排名还要往后 24 //(假老板原来在他们这一堆里排第一,现在大BOSS回来了,他的位置现在在d[t]这个位置,往后走了d[t] - 1个位置,他手下的员工地位自然也要后退这么多) 25 } 26 return father[x]; 27 } 28 29 int main() 30 { 31 std::ios::sync_with_stdio(false); 32 int n; 33 cin >> n; 34 35 for (int i = 0; i < N; i++) 36 { 37 father[i] = i; 38 d[i] = 1; 39 num[i] = 1; 40 } 41 42 while (n--) 43 { 44 char c; 45 cin >> c; 46 if (c == 'M') 47 { 48 int x, y; 49 cin >> x >> y; 50 51 int f1 = Find(x); 52 int f2 = Find(y); 53 if(f1 != f2) 54 { 55 father[f2] = f1; //f1公司吞并了f2,让f1做f2的大老板 56 d[f2] = num[f1] + 1;//f2的位置在f1所有员工之后一位 57 num[f1] += num[f2];//将f2旗下的人都交给新老板f1 58 } 59 } 60 else 61 { 62 int m; 63 cin >> m; 64 int cnt = Find(m); 65 cout << num[cnt] - d[m] << endl;//公司总人数减去她的位置,就是地位在她之下的人的数量 66 } 67 } 68 return 0; 69 }
Loading...