并查集

一、鄙人的浅薄认知:给你一些具有父子关系的数据,你的任务就是把他们家家谱汇总出来,然后,顺便记录一些年龄啊,几个孩子什么的,最后回答问题就行了。

二、典型例题

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\n", 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:\n",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\n",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.\n");
63                     else printf("In different gangs.\n");
64                 }
65                 else//有一个不在集合里就莫得办法了 
66                     printf("Not sure yet.\n");
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:\n", t++);
69         if (flag)
70             printf("No suspicious bugs found!\n\n");
71         else
72             printf("Suspicious bugs found!\n\n");
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...

转载于:https://www.cnblogs.com/xiaohanghuo/p/11366470.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值