zoukankan      html  css  js  c++  java
  • Abandon の 并查集【专辑】(长期更新)

    并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。

    并查集(Disjoint-set data structure ---from wiki )的题目大体分为三个:普通的并查集带种类的并查集扩展的并查集(主要是必须指定合并时的父子关系,或者统计一些数据,比如此集合内的元素数目。)

    普通并查集

    比较简单、直接的不相交集合的合并和查询问题。此类并查集一般表示:如果a、b同集则表示a、b同类;否则a、b不同类。

    模板:

    普通并查集模板
     1 //普通并查集,加上了路径压缩和Rank合并的优化
     2   
     3 const int N=100005;  
     4   
     5 struct set  
     6 {  
     7     int parent;  //记录父节点  
     8     int rank;    //记录集合的节点数  
     9 }elem[N];  
    10   
    11 int MAX;      //最大集的元素个数
    12   
    13 void init()  
    14 {  
    15     int i;  
    16     for(i=0;i<=N;i++)  
    17     {  
    18         elem[i].parent=i;  
    19         elem[i].rank=1;  
    20     }  
    21 }  
    22   
    23 int Find(int x)  
    24 {  
    25     if (elem[x].parent != x)                //路径压缩
    26     {
    27     elem[x].parent = Find(elem[x].parent);
    28     }
    29     return elem[x].parent; 
    30 }  
    31   
    32 void Union(int a,int b)   //合并两个集合  
    33 {  
    34     int x,y;  
    35     x=Find(a);  
    36     y=Find(b);  
    37     if(elem[x].rank>=elem[y].rank)  
    38     {  
    39         elem[y].parent=elem[x].parent;  
    40         elem[x].rank+=elem[y].rank;  
    41         if(MAX<elem[x].rank)  
    42             MAX=elem[x].rank;  
    43     }  
    44     else  
    45     {  
    46         elem[x].parent=elem[y].parent;  
    47         elem[y].rank+=elem[x].rank;  
    48         if(MAX<elem[y].rank)  
    49             MAX=elem[y].rank;  
    50     }  
    51 }  

      ♠POJ 2524 Ubiquitous Religions (并查集入门 || 第一个并查集程序)

      直接套模板即可。。。

    POJ 2524
      1 #include <fstream>
      2 #include <iostream>
      3 #include <cstdio>
      4 #include <cstdlib>
      5 #include <cmath>
      6 #include <iomanip>
      7 #include <iomanip>
      8 #include <climits>
      9 #include <vector>
     10 #include <stack>
     11 #include <queue>
     12 #include <list>
     13 #include <set>
     14 #include <map>
     15 #include <algorithm>
     16 #include <string>
     17 #include <cstring>
     18 
     19 using namespace std;
     20 
     21 #define N 50005
     22 
     23 //并查集
     24 struct set
     25 {
     26     int parent;  //记录父节点
     27     int rank;    //记录集合的节点数
     28 }elem[N];
     29 
     30 int MAX;
     31 int vis[N];
     32 
     33 void init()
     34 {
     35     int i;
     36     for(i=0;i<=N;i++)
     37     {
     38         elem[i].parent=i;
     39         elem[i].rank=1;
     40     }
     41 }
     42 
     43 int Find(int x)
     44 {
     45     int root,temp;
     46     temp=x;
     47     while(x!=elem[x].parent)    //寻找根节点
     48         x=elem[x].parent;
     49     root=x;
     50     x=temp;
     51     while (x!=elem[x].parent)   //压缩路径,全部赋值为根节点的值
     52     {
     53         temp=elem[x].parent;
     54         elem[x].parent=root;
     55         x=temp;
     56     }
     57     return root;
     58 }
     59 
     60 void Union(int a,int b)   //合并两个集合
     61 {
     62     int x,y;
     63     x=Find(a);
     64     y=Find(b);
     65     if(elem[x].rank>=elem[y].rank)
     66     {
     67         elem[y].parent=elem[x].parent;
     68         elem[x].rank+=elem[y].rank;
     69         if(MAX<elem[x].rank)
     70             MAX=elem[x].rank;
     71     }
     72     else
     73     {
     74         elem[x].parent=elem[y].parent;
     75         elem[y].rank+=elem[x].rank;
     76         if(MAX<elem[y].rank)
     77             MAX=elem[y].rank;
     78     }
     79 }
     80 
     81 int main()
     82 {
     83     int n,m;
     84     int tt=0;
     85     while(cin>>n>>m)
     86     {
     87         tt++;
     88         int ans=0;
     89         memset(vis,0,sizeof(vis));
     90         if (!n && !m)
     91             return 0;
     92         init();
     93         for (int i=0;i<m;i++)
     94         {
     95             int a,b;
     96             cin>>a>>b;
     97             Union(a,b);
     98         }
     99         for (int i=1;i<=n;i++)
    100             if (!vis[Find(i)])
    101             {
    102                 ans++;
    103                 vis[Find(i)]=1;
    104             }
    105         cout<<"Case "<<tt<<": "<<ans<<endl;
    106     }
    107 
    108     return 0;
    109 }

     

    ♦种类并查集

    较复杂的并查集题目,此类并查集表示的已经不是同类、不同类的问题,而是a、b同集则表示a、b有关系;不同集表示a、b没关系。而同集有关系中又包含不同的种类问题。比如亲戚和非亲戚,而亲戚中有包含父亲、儿子等等关系,所以在同意并查集中处理不同种类比较麻烦。    

    关键词:标记、向量思维

    模板:

    种类并查集模板
     1 //种类并查集 ,Rank合并在这里就不用了,方便向量的汇总。
     2 
     3 const int N=100005;
     4 
     5 struct set
     6 {
     7     int parent;  //记录父节点
     8     int rank;    //记录集合的节点数
     9     int relation;
    10 }elem[N];
    11 
    12 int MAX;      //最大集的元素个数
    13 
    14 void init()
    15 {
    16     int i;
    17     for(i=0;i<=N;i++)
    18     {
    19         elem[i].parent=i;
    20         elem[i].rank=1;
    21         elem[i].relation=0;
    22     }
    23 }
    24 
    25 int Find(int x)
    26 {
    27     int temp;
    28     if (elem[x].parent != x)                        //路径压缩
    29     {
    30         temp=elem[x].parent;
    31         elem[x].parent = Find(elem[x].parent);
    32         elem[x].relation=(elem[temp].relation + elem[x].relation)%3;
    33     }
    34     return elem[x].parent;
    35 }
    36 
    37 void Union(int d,int a,int b)                         //合并两个集合
    38 {
    39     int x,y;
    40     x=Find(a);
    41     y=Find(b);
    42     elem[x].parent=y;
    43     elem[x].relation=(elem[b].relation-elem[a].relation+2+d)%3;
    44 }

      ♠POJ 1182 食物链 (经典种类并查集)

      思路:

    种类并查集思路总结
    题目告诉有3种动物,互相吃与被吃,现在告诉你m句话,其中有真有假,叫你判断假的个数(如果前面没有与当前话冲突的,即认为其为真话)
    这题有几种做法,我以前的做法是每个集合(或者称为子树,说集合的编号相当于子树的根结点,一个概念)中的元素都各自分为A, B, C三类,在合并时更改根结点的种类,其他点相应更改偏移量。但这种方法公式很难推,特别是偏移量很容易计算错误。
    下面来介绍一种通用且易于理解的方法:
    首先,集合里的每个点我们都记录它与它这个集合(或者称为子树)的根结点的相对关系relation。0表示它与根结点为同类,1表示它吃根结点,2表示它被根结点吃。
    那么判断两个点a, b的关系,我们令p = Find(a), q = Find(b),即p, q分别为a, b子树的根结点。
           1. 如果p != q,说明a, b暂时没有关系,那么关于他们的判断都是正确的,然后合并这两个子树。这里是关键,如何合并两个子树使得合并后的新树能保证正确呢?这里我们规定只能p合并到q(刚才说过了,启发式合并的优化效果并不那么明显,如果我们用启发式合并,就要推出两个式子,而这个推式子是件比较累的活…所以一般我们都规定一个子树合到另一个子树)。那么合并后,p的relation肯定要改变,那么改成多少呢?这里的方法就是找规律,列出部分可能的情况,就差不多能推出式子了(对于任给的一个模型,如何快速推出式子?看一看这个博客里另一篇向量的思维模式吧~~~)。这里式子为 : tree[p].relation = (tree[b].relation – tree[a].relation + 2 + d) % 3; 这里的d为判断语句中a, b的关系。还有个问题,我们是否需要遍历整个a子树并更新每个结点的状态呢?答案是不需要的,因为我们可以在Find()函数稍微修改,即结点x继承它的父亲(注意是前父亲,因为路径压缩后父亲就会改变),即它会继承到p结点的改变,所以我们不需要每个都遍历过去更新。
           2. 如果p = q,说明a, b之前已经有关系了。那么我们就判断语句是否是对的,同样找规律推出式子。即if ( (tree[b].relation + d + 2) % 3 != tree[a].relation ), 那么这句话就是错误的。
           3. 再对Find()函数进行些修改,即在路径压缩前纪录前父亲是谁,然后路径压缩后,更新该点的状态(通过继承前父亲的状态,这时候前父亲的状态是已经更新的)。
           核心的两个函数为:
           int Find(int x)
           {
               int temp_p;
              if (tree[x].parent != x)
              {
                  // 因为路径压缩,该结点的与根结点的关系要更新(因为前面合并时可能还没来得及更新).
                  temp_p = tree[x].parent;
                  tree[x].parent = Find(tree[x].parent);
                  // x与根结点的关系更新(因为根结点变了),此时的temp_p为它原来子树的根结点.
                  tree[x].relation = (tree[x].relation + tree[temp_p].relation) % 3;
              }
              return tree[x].parent;
           }
           void Merge(int a, int b, int p, int q, int d)
           {
              // 公式是找规律推出来的.
              tree[p].parent = q; // 这里的下标相同,都是tree[p].
              tree[p].relation = (tree[b].relation – tree[a].relation + 2 + d) % 3;
           }
           而这种纪录与根结点关系的方法,适用于几乎所有的并查集判断关系(至少我现在没遇到过不适用的情况…可能是自己做的还太少了…),所以向大家强烈推荐~~
           搞定了食物链这题,基本POJ上大部分基础并查集题目就可以顺秒了,这里仅列个题目编号: POJ 1308 1611 1703 1988 2236 2492 2524
    我对向量思维的理解(结合本题)
    有两种方法:
        一:用的3倍数组,1~n是自己,n+1~2n是吃域,2n+1~3n是被吃域。然后x,y同类就x,y并起来,x+n,y+n并,x+2n,y+2n并,否则就和对方的吃域被吃域乱七八糟的并一并,然后Find时就找是在吃域里还是被吃域里。。。。。。
     
        二:带相对偏移量的并查集:
          用一个并查集表示两个元素有没有关系,然后在并查集里设置一个附属的相对偏移量,0表示和根节点同类,1表示吃根节点,2表示被根节点吃。
          向量的思维模式:
            > 什么叫做向量的思维模式?
            > Orz Orz
            我的理解是,对于集合里的任意两个元素a,b而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的,否则也不会被合并到当前集合中。那么我们就把这2个元素之间的关系量转化为一个偏移量,以食物链的关系而言,不妨假设
              a->b 偏移量0时 a和b同类
              a->b 偏移量1时 a吃b
              a->b 偏移量2时 a被b吃,也就是b吃a
            有了这些基础,我们就可以在并查集中完成任意两个元素之间的关系转换了。
            不妨继续假设,a的当前集合根节点aa,b的当前集合根节点bb,a->b的偏移值为d-1(题中给出的询问已知条件)
            (1)如果aa和bb不相同,那么我们把bb合并到aa上,并且更新delta[bb]值(delta[i]表示i的当前集合根节点到i的偏移量)
              此时 aa->bb = aa->a + a->b + b->bb,可能这一步就是所谓向量思维模式吧
              上式进一步转化为:aa->bb = (delta[a]+d-1+3-delta[b])%3 = delta[bb],(模3是保证偏移量取值始终在[0,2]间)
            (2)如果aa和bb相同,那么我们就验证a->b之间的偏移量是否与题中给出的d-1一致
              此时 a->b = a->aa + aa->b = a->aa + bb->b,
              上式进一步转化为:a->b = (3-delta[a]+delta[b])%3,
            若一致则为真,否则为假。

    --------------------------------------------------------------------------------------------------------------------------------------------------------

    POJ 1182
      1 #include <iostream>
      2 #include <cstdio>
      3 #include <cstdlib>
      4 #include <cmath>
      5 #include <iomanip>
      6 #include <climits>
      7 #include <vector>
      8 #include <stack>
      9 #include <queue>
     10 #include <set>
     11 #include <map>
     12 #include <algorithm>
     13 #include <string>
     14 #include <cstring>
     15 
     16 using namespace std;
     17 
     18 typedef long long ll;
     19 const double EPS = 1e-11;
     20 
     21 void Swap(int &a,int &b){   int t=a;a=b;b=t; }
     22 int Max(int a,int b)    {   return a>b?a:b;  }
     23 int Min(int a,int b)    {   return a<b?a:b;  }
     24 
     25 const int N=100005;
     26 
     27 struct set
     28 {
     29     int parent;  //记录父节点
     30     int rank;    //记录集合的节点数
     31     int relation;
     32 }elem[N];
     33 
     34 int MAX;      //最大集的元素个数
     35 
     36 void init()
     37 {
     38     int i;
     39     for(i=0;i<=N;i++)
     40     {
     41         elem[i].parent=i;
     42         elem[i].rank=1;
     43         elem[i].relation=0;
     44     }
     45 }
     46 
     47 int Find(int x)
     48 {
     49     int temp;
     50     if (elem[x].parent != x)                //路径压缩
     51     {
     52         temp=elem[x].parent;
     53         elem[x].parent = Find(elem[x].parent);
     54         elem[x].relation=(elem[temp].relation + elem[x].relation)%3;
     55     }
     56     return elem[x].parent;
     57 }
     58 
     59 void Union(int d,int a,int b)                     //合并两个集合
     60 {
     61     int x,y;
     62     x=Find(a);
     63     y=Find(b);
     64     elem[x].parent=y;
     65     elem[x].relation=(elem[b].relation-elem[a].relation+2+d)%3;
     66 }
     67 
     68 int main()
     69 {
     70     int n,k;
     71     scanf("%d%d",&n,&k);
     72     init();
     73     int num=0;
     74     for (int i=0;i<k;i++)
     75     {
     76         int d,a,b;
     77         scanf("%d%d%d",&d,&a,&b);
     78         if (d==2 && a==b)
     79         {
     80             num++;
     81             continue;
     82         }
     83         if (a>n ||b>n)
     84         {
     85             num++;
     86             continue;
     87         }
     88         if (Find(a)!=Find(b))
     89         {
     90             Union(d,a,b);
     91         }
     92         else
     93         {
     94             if ((elem[b].relation+d+2)%3 != elem[a].relation )
     95                 num++;
     96         }
     97 
     98     }
     99     printf("%d\n",num);
    100     return 0;
    101 }

      ♠POJ 1703 Find them, Catch them

      思路:

      和食物链那道题很像。只不过这里只有两个相对种类偏移量:0表示和根节点同帮派,1表示和根节点异帮派。

      然后根据向量思维退出并查集中改变关系的式子:

      1. aa->bb=aa->a+a->b+b->bb

        在Union中:elem[y].relation=(elem[a].relation+1+elem[b].relation)%2;

      2. aa->b=aa->bb+bb->b

        在Find路径压缩时:elem[x].relation=(elem[elem[x].parent].relation+elem[x].relation)%2;

      3.同理,判断时:1 or 0==a->b=a->aa+aa->b

    -------------------------------------------------------------------------------------------------------------------------------------------------------- 

    POJ 1703

    ♦拓展并查集

     

      (未完待续。。。)

    其他待做并查集题目:

    POJ-1308

    用并查集来判断一棵树。。注意空树也是树,死人也是人。

    POJ-1611

    裸地水并查集

    POJ-1988

    看上去似乎和种类并查集无关,但其实仔细想想,就是种类并查集。。。
    只不过是种类数目无穷大,通过合并,可以确定两个物品之间的种类差(即高度差)

    POJ-2236

    裸地并查集,小加一点计算几何

    POJ-2492

    裸地种类并查集

    POJ-1456

    常规思想是贪心+堆优化,用并查集确实很奇妙。。。下面的文章中有详细介绍。

    POJ-1733

    种类并查集,先要离散化一下,不影响结果。。。

    HDU-3038

    上一道题的扩展,也是种类并查集,种类无穷大。。。。

    POJ-1417

    种类并查集,然后需要背包原理来判断是否能唯一确定“好人”那一堆

    POJ-2912

    ZOJ-3261

    逆向使用并查集就可以了。。。

    POJ-1861  POJ-2560

    Kruskal并查集

    举杯独醉,饮罢飞雪,茫然又一年岁。 ------AbandonZHANG
  • 相关阅读:
    61. 最长不含重复字符的子字符串
    60. 礼物的最大价值 (未理解)
    59. 把数字翻译成字符串
    58. 把数组排成最小的数
    57. 数字序列中某一位的数字 (不懂)
    spring data jpa 官方文档
    idea 编译报错 源发行版 1.8 需要目标发行版 1.8
    idea maven 依赖报错 invalid classes root
    solr
    spring boot 官方文档
  • 原文地址:https://www.cnblogs.com/AbandonZHANG/p/2682491.html
Copyright © 2011-2022 走看看