并查集应用 —— POJ 1182 食物链

来源:互联网 发布:数控车床编程视频教程 编辑:程序博客网 时间:2024/05/16 17:56

对应 POJ 题目:点击打开链接

食物链
Time Limit: 1000MS Memory Limit: 10000KTotal Submissions: 54927 Accepted: 16104

Description

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这N个动物所构成的食物链关系进行描述: 
第一种说法是"1 X Y",表示X和Y是同类。 
第二种说法是"2 X Y",表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。 

Input

第一行是两个整数N和K,以一个空格分隔。 
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 
若D=1,则表示X和Y是同类。 
若D=2,则表示X吃Y。

Output

只有一个整数,表示假话的数目。

Sample Input

100 71 101 1 2 1 22 2 3 2 3 3 1 1 3 2 3 1 1 5 5

Sample Output

3

题意:中文题,不多说


思路:

        开始时把同一类的动物并在相同的集合往死里想,做了一堆无用功;首先要正确理解的一点是:要把相互之间有关系(吃与被吃关系,同类关系)的,或者他们之间的关系可以通过集合所构成的树推导出来的动物(比如x, y)并在相同的集合

                                                        


        如上图,在这棵以 5 为根的树中,其中每个结点之间都存在关系,比如 1 和 2 之间,他们的关系要么是1 吃 2; 要么是  2 吃 1 ;要么1 和 2 是同类。确定他们之间关系的方法将重点讲。

        我们用 fa[x] = y 表示 x 的父亲结点为 y;然后用一个rela[] 数组表示 x 与 fa[x] 的关系。

        rela[x] = 0 表示 x 与 fa[x] 是同类,

        rela[x] = 1 表示 x 吃 fa[x],

        rela[x] = 2 表示 fa[x] 吃 x。


        看这样一颗3个结点的树,

       (图1)             (图2)   

        假如此时rela[x] = 1;relax[y] = 1;relax[z] = 0(也只能等于0,因为它是根,自己跟自己是同类)

        即是 x 吃 y ,y 吃 z ;由这两点再根据题目意思还可以知道 z 吃 x

        当我们对 x 调用 fx = Find(x) 时,这时必须知道什么是并查集的路径压缩,不知道的先弄懂再往下看;

        我们就会把(图1 )变成(图2),但此时rela[x] = 1 就错了(这样就是 x 吃 z ,与原意 z 吃 x 不符)

        所以在压缩路径的时候要修改 rela[x] 的值,怎样修改?!

        其实我们可以根据 x 对 y 的关系,以及 y 对 z 的关系,推导出 x 对 z 的关系。枚举这个过程就是:

        rela[x]        rela[y]          rela[z]     |     修改后的 rela[x]

           0                0                  0                                  0

           0                1                  0                                  1

           0                2                  0                                  2

           1                0                  0                                  1

           1                1                  0                                  2

           1                2                  0                                  0

           2                0                  0                                  2

           2                1                  0                                  0

           2                2                  0                                  1

        通过枚举所有情况我们发现

        修改后的 rela[x] = (rela[x] + rela[y]) % 3;

        因此在令 z = fa[x] 之前加上这句:rela[x] = (rela[x] + rela[y]) % 3  就能正确描述结点之间的关系。 

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

        这样对于一个操作 d  x  y;我们可以这样处理:

        令 fx = Find(x);   fy = Find(y);    如果 fx == fy ;即x 与 y 在同一个集合里。这好办,根据路径压缩我们会得到这样的一棵树:

                     

        即 x 与 y 都直接指向根结点(也就是集合的代表)。这时我们知道的是 x 对 root 的关系 rela[x] 和 y 对 root 的关系 rela[y];问题是我们要知道的是 x 对 y 的关系,因为我们要以此来判断 d   x   y 是否正确。

        如果我们把上图想象成(只是想象,实际并不进行这样的操作):

                              即改变了root 与 y 的父子关系

        rela[x] 没有变,我们不去动它;但 rela[root] 不再是 0 ,这里我直接说了,通过枚举我们可以知道 rela[root] = 3 - rela[y]。接下来有没有熟悉的感觉,就是上面(图1)的那个过程;这里就可以确定 x 与 y 的关系了。即 x 对 y 的关系 rxy = (rela[x] + rela[root]) % 3;展开就是 rxy = (rela[x] + (3 - rela[y])) % 3;

        接下来如果 rxy != d - 1 的话,那 d   x   y 这句话就是假的(把 d = 1 和 d = 2 代入你会发现的确是这样)


        如果 fx != fy 呢?这就说明x 与 y 不在同一个集合里面  。这更好办,说明 x 与 y 还没有任何关系,你怎样说都是对的。麻烦的是定义了x 与 y 的关系之后(比如 1  x  y  或 2  x  y),你要把 x 所在的集合跟 y 所在的集合合并,使得 x 与 y 存在关系。

        我们通过 fx = Find(x) 和 fy = Find(y) 同样可以得到这样图:

          

           为了方便观察我们可以简化这个图:

                         

        这里我们把 x 所在集合接到 y 所在集合里,即最终令 fa[fx] = fy,即

                          

         但在这之前我们要先确定 fx 对 fy 的关系,即修改 rela[fx]。怎样修改?我们通过 d  x  y  操作可以知道 x 对 y 的关系为 d - 1 (d = 1 那 d - 1  = 0 就表示 x 与 y 是同类,d = 2 那 d - 1  = 1 就表示 x 吃 y,跟我们的定义是一样的);即我们知道了 x 对 y 的关系,y 对 fy 的关系,那根据前面的公式, x 对 fy 的关系 x_fy = ((d - 1) + rela[y]) % 3。到这里我们可以想象到这样一个图:

                          

        这里我们知道 x 对 fx 的关系 rela[x] 和 x 对 fy 的关系 x_fy;那我们可以像前面那样改变 x 与 fx 的父子关系,使图变成这样:

                        

        相应地可以求 fx 对 x 的关系为 fx_x = 3 - rela[x];这样就可以求 fx 对 fy 的关系了!即 fx 对 fy 的关系 fx_fy = (fx_x + x_fy) % 3。把原数代入就是:

         fx_fy = ((3 - rela[x]) + ((d - 1) + rela[y]) % 3) % 3          也就是:

         fx_fy = (2 - rela[x] + d + rela[y]) % 3    

        这样就可以合并两个集合了。

        必须叹服!这道题把并查集运用地非常巧妙!

#include <stdio.h>#include <stdlib.h>#include <string.h>#define N 50010int fa[N];int rela[N];void Init(){int i;for(i = 0; i < N; i++){fa[i] = i;rela[i] = 0;}}int Find(int x){int fx;if(x == fa[x]) return x;fx = Find(fa[x]);rela[x] = (rela[x] + rela[fa[x]]) % 3;fa[x] = fx;return fx;}void Union(int x, int y, int d) /* 使 x 所在集合成为 y 所在集合的子集合 */{int fx, fy;fx = fa[x];fy = fa[y];rela[fx] = ((3 - rela[x]) + (d - 1) + rela[y]) % 3;fa[fx] = fy;}int main(){#if 0freopen("in.txt","r",stdin);#endifint n, m, count = 0;scanf("%d%d", &n, &m);Init();while(m--){int d, x, y, fx, fy;scanf("%d%d%d", &d, &x, &y);if(x < 1 || x > n || y < 1 || y > n){count++;continue;}fx = Find(x);fy = Find(y);if(fx == fy){if((rela[x] + (3 - rela[y])) % 3 != d - 1) count++;}else Union(x, y, d);}printf("%d\n", count);return 0;}





0 0
原创粉丝点击