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
  • 相关阅读:
    jupyter notebook 不能画图
    tensorflow中reduce_xxx函数
    tensorflow中用正太分布随机初始化网络权重参数 ---tf.random_normal
    将dataframe变量转为tf.constant变量
    dataframe to tensor
    Python-Pandas 如何shuffle(打乱)数据?
    TensorFlow实战(1)
    基本概念——张量、会话、计算图
    天梯赛 L3-003. 社交集群
    蓝桥杯 算法提高 P0101
  • 原文地址:https://www.cnblogs.com/AbandonZHANG/p/2682491.html
Copyright © 2011-2022 走看看