• 【转载】带权并查集经典例题


      1 #include <cstdio>
      2 #include <cstdlib>
      3 #include <cstring>
      4 #include <iostream>
      5 //#define INPUT
      6 /**
      7     Problem:1182 - 食物链,NOI2001
      8     Begin Time:4th/Mar/2012 1:00 p.m.
      9     End Time:4th/Mar/2012 6:47 p.m.
     10     Cost Time:两天多,看的别人的解题报告AC的
     11     Reference:http://apps.hi.baidu.com/share/detail/16059767
     12     测试数据:
     13     http://poj.org/showmessage?message_id=93058
     14     输出:
     15     上方有
     16     教训:
     17         WA一次,没搞清楚先更新父节点relation还是更新当前节点relation的关系!!!
     18         (在最后那条犯错误了!)
     19     思路:
     20     老子决心要写一个,关于这道题的,最详细的解题报告。
     21     本题思路是带权并查集,我们从最开始讲起。
     22     Part I  - 权值(relation)的确定。
     23     我们根据题意,森林中有3种动物。A吃B,B吃C,C吃A。
     24     我们还要使用并查集,那么,我们就以动物之间的关系来作为并查集每个节点的
     25     权值。
     26     注意,我们不知道所给的动物(题目说了,输入只给编号)所属的种类。
     27     所以,我们可以用动物之间“相对”的关系来确定一个并查集。
     28     0 - 这个节点与它的父节点是同类
     29     1 - 这个节点被它的父节点吃
     30     2 - 这个节点吃它的父节点。
     31     注意,这个0,1,2所代表的意义不是随便制定的,我们看题目中的要求。
     32     说话的时候,第一个数字(下文中,设为d)指定了后面两种动物的关系:
     33     1 - X与Y同类
     34     2 - X吃Y
     35     我们注意到,当 d = 1的时候,( d - 1 ) = 0,也就是我们制定的意义
     36                 当 d = 2的时候,( d - 1 ) = 1,代表Y被X吃,也是我们指定的意义。
     37     所以,这个0,1,2不是随便选的
     38     Part II - 路径压缩,以及节点间关系确定
     39     确定了权值之后,我们要确定有关的操作。
     40     我们把所有的动物全初始化。
     41     struct Animal
     42     {
     43         int num; //该节点(node)的编号
     44         int parent; //该node的父亲
     45         int relation; //该node与父节点的关系,0同类,1被父节点吃,2吃父节点
     46     }; Animal ani[50010];
     47         初始化为
     48         For i = 0 to N do
     49             ani[i].num = i;
     50             ani[i].parent = i;
     51             ani[i].relation = 0 ; //自己和自己是同类
     52         End For
     53         (1)路径压缩时的节点算法
     54         我们设A,B,C动物集合如下:(为了以后便于举例)
     55         A = { 1 , 2 , 3 ,4 ,5 }
     56         B = { 6 , 7 , 8 ,9 ,10}
     57         C = { 11, 12, 13,14,15}
     58         假如我们已经有了一个集合,分别有3个元素
     59         SET1 = {1,2},我们规定集合中第一个元素为并查集的“代表”
     60         假如现在有语句:
     61         2 2 6
     62         这是一句真话
     63         2是6的父亲
     64          ani[6].parent = 2;
     65          ani[6].relation = 1;
     66         那么,6和1的关系如何呢?
     67          ani[2].parent = 1;
     68          ani[2].relation = 0;
     69         我们可以发现6与2的关系是 1.
     70         通过穷举我们可以发现
     71         ani[now].parent = ani[ani[now].parent].parent;
     72         ani[now].relation = ( ani[now].relation + ani[now.parent].relation ) % 3;
     73         这个路径压缩算法是正确的
     74         关于这个路径压缩算法,还有一点需要注意的地方,我们一会再谈
     75         注意,根据当前节点的relation和当前节点父节点的relation推出
     76         当前节点与其父节点的父节点的relation这个公式十分重要!!
     77         它推不出来下面都理解不了!!自己用穷举法推一下:
     78         好吧,为了方便伸手党,我给出穷举过程
     79                 i      j
     80         爷爷  父亲  儿子  儿子与爷爷
     81                0      0       (i + j)%3 = 0
     82                0      1       (i + j)%3 = 1
     83                0      2       (i + j)%3 = 2
     84                1      0       (i + j)%3 = 1
     85                1      1       (i + j)%3 = 2
     86                1      2       (i + j)%3 = 0
     87                2      0       (i + j)%3 = 2
     88                2      1       (i + j)%3 = 0
     89                2      2       (i + j)%3 = 1
     90         嗯,这样可以看到,( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation
     91         这就是路径压缩的节点算法
     92         (2) 集合间关系的确定
     93         在初始化的时候,我们看到,每个集合都是一个元素,就是他本身。
     94         这时候,每个集合都是自洽的(集合中每个元素都不违反题目的规定)
     95         注意,我们使用并查集的目的就是尽量的把路径压缩,使之高度尽量矮
     96         假设我们已经有一个集合
     97         set1 = {1,2,7,10}
     98         set2 = {11,4,8,13},每个编号所属的物种见上文
     99         set3 = {12,5,4,9}
    100         现在有一句话
    101         2 13 2
    102         这是一句真话,X = 13,Y = 2
    103         我们要把这两个集合合并成一个集合。
    104         直接
    105         int a = findParent(ani[X]);
    106         int b = findParent(ani[Y]);
    107         ani[b].parent = a;
    108         就是把Y所在集合的根节点的父亲设置成X所在集合的根节点。
    109         但是,但是!!!!
    110         Y所在集合的根结点与X所在集合的根节点的关系!!!要怎么确定呢?
    111         我们设X,Y集合都是路径压缩过的,高度只有2层
    112         我们先给出计算的公式
    113         ani[b].relation = ( 3 - ani[Y].relation + ( d - 1 ) + ani[X].relation) % 3;
    114         这个公式,是分三部分,这么推出来的
    115         第一部分,好理解的一部分:
    116         ( d - 1 ) :这是X和Y之间的relation,X是Y的父节点时,Y的relation就是这个
    117         3 - ani[Y].relation = 根据Y与根节点的关系,逆推根节点与Y的关系
    118         这部分也是穷举法推出来的,我们举例:
    119         j
    120         子         父相对于子的relation(即假如子是父的父节点,那么父的relation应该是什么,因为父现在是根节点,所以父.relation = 0,我们只能根据父的子节点反推子跟父节点的关系)
    121          0             ( 3 - 0 ) % 3 = 0
    122          1(父吃子)   ( 3 - 1 ) % 3 = 2 //父吃子
    123          2(子吃父)    ( 3 - 2 ) % 3 = 1 //子吃父,一样的哦亲
    124         ——————————————————————————————————————————————————————
    125         我们的过程是这样的:
    126         把ani[Y],先连接到ani[X]上,再把ani[Y]的根节点移动到ani[X]上,最后,把ani[Y]的根节点移动到ani[X]的根节点上,这样算relation的
    127         还记得么,如果我们有一个集合,压缩路径的时候父子关系是这么确定的
    128         ani[爷爷].relation = ( ani[父亲].relation + ani[儿子].relation ) % 3
    129         我们已知道,( d - 1 )就是X与Y的relation了
    130         而 (3 - ani[Y].relation)就是 以Y为根节点时,他的父亲的relation
    131         那么
    132         我们假设把Y接到X上,也就说,现在X是Y的父亲,Y原来的根节点现在是Y的儿子
    133           Y的relation   +     ani[Y]根节点相对于ani[Y]的relation
    134         ( ( d - 1 )         +    ( 3 - ani[Y].relation) ) % 3
    135         就是ani[Y]的父亲节点与ani[X]的relation了!
    136         那么,不难得到,ani[Y]的根节点与ani[X]根节点的关系是:
    137         ( ( d - 1 ) + ( 3 - ani[Y].relation) + ani[X].relation ) % 3 ->应用了同余定理
    138         注意,这个当所有集合都是初始化状态的时候也适用哦
    139         还是以最开头我们给的三个集合(分别代表三个物种)为例
    140         2 1 6
    141         带入公式
    142         ani[6].relation = ( ( 2 - 1 ) + ( 3 - 0 ) + 0 ) % 3 = 1
    143         也就是,6被1吃
    144     Part III - 算法正确性的证明
    145         首先,两个自洽的集合,合并以后仍然是自洽的
    146         这个不难想吧,数学上有个什么对称性定理跟他很像的。
    147         如果理解不了,就这么想!!
    148         当set1和set2合并之后,set2的根节点得到了自己关于set1根节点的
    149         正确relation值,变成了set1根节点的儿子,那么
    150         set2的所有儿子只要用
    151         ( ani[X].relation + ani[Y].relation ) % 3就能得到自己正确的relation值了
    152         所以说,针对不在同一集合的两个元素的话,除非违背了(2)和(3),否则永远是真的
    153         (无论这句话说的是什么,我们都可以根据所给X,Y推出两个子节点之间应有的关系,这个关系一确定,所有儿子的关系都可以确定)
    154         其实所有的不同集合到最后都会被合并成一个集合的。
    155         我们只要在一个集合中找那些假话就可以了。
    156         首先,如何判断
    157         1 X Y是不是假话。//此时 d = 1
    158         if ( X 和 Y 不在同一集合)
    159             Union(x,y,xroot,yroot,d)
    160         else
    161             if x.relation != y.relation  ->假话
    162         其次,如何判断
    163         2 X Y是不是假话 //此时d = 2
    164         if ( X 和 Y 不在同一集合)
    165             Union(x,y,xroot,yroot,d)
    166         else
    167             (ani[y].relation + 3 - ani[x].relation ) % 3 != 1 ->假话
    168         这个公式是这么来的:
    169         3 - ani[x].relation得到了根节点关于x的relation
    170         ani[y] + 3 - ani[x].relation得到了y关于x的relation
    171         所以,只要y关于x的relation不是1,就是y不被x吃的话,这句话肯定是假话!
    172 
    173         (2)路径压缩要特别注意的一点(错在这里,要检讨自己)
    174             路径压缩的时候,记得要
    175             先findParent,再给当前节点的relation赋值。
    176             否则有可能因为当前节点的父节点的relation不正确而导致错的稀里哗啦。
    177             例子:
    178             set1 = {1,2,7,10}
    179             set2 = {3,4,8,11}
    180             set3 = {12,5,14,9}
    181             Union(1,3,1,3,1)
    182             Union(3,12,3,12,2)
    183             1 5 1
    184             算5的relation
    185             如果不先更新parent的relation,算出来应该是
    186             ( 3 - 0 + 0 + 1 ) % 3 = 1,5被1吃,显然不对
    187             这里面,+ 0的那个0是指根节点 12 的relation(未更新,这里的0是指12与11的relation)
    188             如果更新完了的话,应该是
    189             ( 3 - 0 + 2 + 1 ) % 3 = 0 ,5与1是同一物种,对了
    190             这里面的 2 是更新节点12的relation(12与1的relation)
    191     后记:
    192         关于这道题,我在网上搜索了许多解题报告,但是都闪烁其词,大概大家都不想
    193         把自己辛辛苦苦推出来的公式写到网上供别人学习来节省时间吧。
    194         我觉得这么做不好,对初学者容易产生不良影响,ACM如果只是一个小众化的圈子,那
    195         岂不是太没意思了。
    196         于是我就把我自己总结的这道题的经验放了出来,希望可以帮得到大家
    197         自己总结的,对错也不知道,但是起码是“自洽”的,^ ^
    198         感谢那篇博文的博主,也感谢gzm,lqy两位学长的指导。
    199         c0de4fun
    200 */
    201 using namespace std;
    202 const int c0de4fun = 50010;//动物个数的最大值
    203 ///指明父节点与自己的关系,0同类,1被吃,2吃父
    204 const int SAME = 0;
    205 const int ENEMY = 1;
    206 const int FOOD = 2;
    207 struct Animal
    208 {
    209     int parent;
    210     int num;
    211     int relation;
    212 };
    213 Animal ani[c0de4fun];
    214 long ans;
    215 int findParent(Animal* node)
    216 {
    217     ///Wrong Answer 因为这个函数写错了
    218     ///这个函数得是“自洽的”
    219     ///就是说,得保证每个元素的父亲的relation是对的
    220     ///再算自己的relation
    221     ///因为自己的relation和父亲的relation有关
    222     ///这就是为什么要先findParent再relation更新的原因
    223     int tmp;
    224     if( node->parent == node->num )
    225         return node->parent;
    226     tmp = node->parent;
    227 #ifdef DBG
    228     printf("Animal %d s Parent is %d
    ",node->num,node->parent);
    229 #endif
    230    // node->relation = ( ani[node->parent].relation + node->relation ) % 3;
    231     node->parent = findParent(&ani[node->parent]);
    232     node->relation = ( ani[tmp].relation + node->relation ) % 3;
    233     return node->parent;
    234 }
    235 void Union(int x,int y,int a,int b,int d)
    236 {
    237     ani[b].parent = a;
    238     ///rootY.parent = rootX.parent;
    239     ani[b].relation =( (3 - ani[y].relation) + (d - 1) + ani[x].relation) % 3;
    240 }
    241  
    242 void init_Animal(int n)
    243 {
    244     for(int i = 1 ; i <= n ; i++)
    245     {
    246         ani[i].num = i;
    247         ani[i].parent = i;
    248         ani[i].relation = SAME;
    249     }
    250 }
    251 int main(int argc,char* argv[])
    252 {
    253     int N,K;
    254     int d,X,Y;
    255 #ifdef INPUT
    256     freopen("b:\acm\poj1182\input.txt","r",stdin);
    257 #endif
    258     scanf("%d%d",&N,&K);
    259     init_Animal(N);
    260     for(int i = 0 ; i < K ; i++)
    261     {
    262         scanf("%d%d%d",&d,&X,&Y);
    263         if( X > N || Y > N)
    264             ans++;
    265         else
    266         {
    267             if(d == 2 && X == Y)
    268                 ans++;
    269             else
    270             {
    271                 int a = findParent(&ani[X]);
    272                 int b = findParent(&ani[Y]);
    273                 if ( a != b )
    274                 {
    275                     ///x,y不在同一集合中
    276                     Union(X,Y,a,b,d);
    277                 }
    278                 else
    279                 {
    280                     switch(d)
    281                     {
    282                         case 1:
    283                             if(ani[X].relation != ani[Y].relation)
    284                                 ans++;
    285                             break;
    286                         case 2:
    287                             if(((ani[Y].relation + 3 - ani[X].relation) % 3 ) != 1)
    288                                 ans++;
    289                             break;
    290                     }
    291                 }
    292             }
    293         }
    294     }
    295     printf("%d
    ",ans);
    296     return 0;
    297 }
  • 相关阅读:
    多维数组,转化为一维数组多种解决方案
    word-wrap与word-break的区别,以及无效情况
    重温前端基础之-js排序算法
    重温前端基础之-css浮动之怪异现象
    重温前端基础之-css浮动与清除浮动
    重温前端基础之-css盒模型
    C# 应用
    C# 应用
    C# 应用
    C# 应用
  • 原文地址:https://www.cnblogs.com/daybreaking/p/12778834.html
Copyright © 2020-2023  润新知