zoukankan      html  css  js  c++  java
  • 并查集

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

    二、典型例题

    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...

  • 相关阅读:
    bzoj3223: Tyvj 1729 文艺平衡树
    bzoj1014: [JSOI2008]火星人prefix
    bzoj3231: [Sdoi2008]递归数列
    bzoj2282: [Sdoi2011]消防
    bzoj3195: [Jxoi2012]奇怪的道路
    成员内部类 局部内部类 匿名内部类
    静态代码块 构造代码块
    父类子类转换
    clone()方法
    后缀表达式求值
  • 原文地址:https://www.cnblogs.com/xiaohanghuo/p/11366470.html
Copyright © 2011-2022 走看看