DisJSet:食物链(POJ 1182)

                          

                动物王国的食物链

  这一题有两种思路,先介绍第一种:

  题目是中文的,我就不翻译了,也很好理解,就是一个A-B-C-A的一个循环的食物链,给定一些条件,问你哪些条件是错的

  这一题,是一道比较经典的查并集的题目,那么这一题的思想是什么呢,对于给定的条件,x和y属于什么集合其实并没有给出,而且x和y之间还有其他的捕食的关系

  但是我们现在假设,如果x和y都是属于同一类的,那么如果x属于A,那么y也一定属于A,那么也就是说,x和y属于同一个集合总是同时成立的的,再假设,如果x可以吃y,那么如果x属于A,那么y一定属于B,x属于B,那么y一定属于C或x属于C,那么y一定属于A,也就是说,在捕食关系中,y属于x的下一个集合一定成立的(A-B-C-A)

  那么我们就可以用查并集来很好的维护这个关系,我们给x和y赋予A,B,C三个属性,并且用查并集来维护这些属性,如果x和y都属于一个集合,那么我们就要分别合并x和y的三个属性,如果属于不是关系,那么我们就把y的与x的下一个关系的集合进行合并,这三个属性可以用k,k+N,k+2*N的形式表现,这样就可以非常快速的维护关系了(因为对于找根的操作而言,查并集是最快的)

  参考《挑战程序设计竞赛 第二版》

  PS:查并集我用的是按大小合并的方式,本来这种方式可以有效的降低搜索的时间,但是因为这一题比较特殊(集合比较多)所以递归时间并没有减少很多,另外大家合并的时候一定要检查是不是同一个集合,不然会出错,并且MLE

  另外这题,真是尼玛,有BUG,只能交单次数据(也就是不能while(~scanf())),真是个脑残bug

  

  

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 
 5 typedef int Position;
 6 
 7 Position Find(Position);
 8 void Union_Set(Position, Position);
 9 int If_Same_Set(Position, Position);
10 
11 static int *Set = NULL;
12 
13 int main(void)
14 {
15     int N, case_sum, i;
16     int ans = 0, inform, x, y;
17     scanf("%d%d", &N, &case_sum);
18     Set = new int[3 * (N + 1)];
19     for (i = 1; i <= 3 * N; i++) Set[i] = -1;
20     for (i = 0; i < case_sum; i++)
21     {
22         scanf("%d%d%d", &inform, &x, &y);
23         if (x > N || y > N)//如果在区域外,直接判断出错
24         {
25             ans++;
26             continue;
27         }
28         else if (inform == 1)
29         {
30             if (If_Same_Set(x, y + N) || If_Same_Set(x, y + 2 * N))
31                 //如果已经是捕食集合中,出错
32                 ans++;
33             else//否则,则把xy的三个属性分别合并
34             {
35                 Union_Set(x, y);
36                 Union_Set(x + N, y + N);
37                 Union_Set(x + 2 * N, y + 2 * N);
38             }
39         }
40         else if (inform == 2)
41         {
42             if (If_Same_Set(x, y) || If_Same_Set(x, y + 2 * N))
43                 //如果已经在非捕食集或已统一集合,出错
44                 ans++;
45             else
46             {
47                 Union_Set(x, y + N);
48                 Union_Set(x + N, y + 2 * N);
49                 Union_Set(x + 2 * N, y);
50             }
51         }
52     }
53     printf("%d
", ans);
54     delete Set;
55     return 0;
56 }
57 
58 Position Find(Position x)
59 {
60     //路径压缩
61     if (Set[x] < 0)
62         return x;
63     else
64         return Set[x] = Find(Set[x]);
65 }
66 
67 int If_Same_Set(Position x, Position y)
68 {
69     return  Find(x) == Find(y);
70 }
71 
72 void Union_Set(Position x, Position y)
73 {
74     Position Rootx, Rooty;
75     Rootx = Find(x);
76     Rooty = Find(y);
77 
78     if (Rootx != Rooty)//按大小求并,千万要注意不能是同一个集合的合并,不然会MLE
79     {
80         if (Set[Rootx] < Set[Rooty])
81         {
82             Set[Rootx] += Set[Rooty];
83             Set[Rooty] = Rootx;
84         }
85         else
86         {
87             Set[Rooty] += Set[Rootx];
88             Set[Rootx] = Rooty;
89         }
90     }
91 }

  第二种思路,只用到一个查并集和一个偏移量集:思路来源http://poj.org/showmessage?message_id=105601

  我们先定义B与A之间的偏移关系:

  存在偏移量集delta,如果|delta[B]-delta[A]|%3,则定义

    =0  B与A是同类

    =1  B是A的食物

    =2  B是A的天敌

  我们规定,每一次关系的确定,最后B与A都会集中到一个集合(广义集合),其中的关系在偏移量集中找

  比如如果规定1吃2,2吃3,4吃2,则说明如果当所有元素的初始化绝对偏移量是0时,则2的绝对偏移量是1,3的绝对偏移量是2(delta[3]-delta[1]=2所以3是1的天敌,符合问题描述),同时4的绝对偏移量是0(绝对偏移量只取012),同样满足关系。但是现在的问题是,我们的集合最后都会搜索到根的,如果我们仅改变某个元素的一次偏移量,可能这个元素就会和根的关系就会发生变化,但是根与其包括的所有元素的关系都是先确定好了,所以我们只用把根的关系都进行偏移就可以,比如我们再定义5吃6,6吃7,8吃6,(偏移量:5-0,6-1,7-2,8-0)同时再定义1吃5,9吃8,如果我们假设5合并到1上,那么5对1的相对偏移量应该是1,而和5这个集合的元素的绝对偏移量应该全部都+1,才能保证相对偏移量不变,也就是可以看成,以5为起点,5这个集合的所有元素全部加上5的绝对偏移量1

  比如处理8吃9的时候,因为5的绝对偏移量为1,所以9的偏移量为delta[9]-delta[8]+5的绝对偏移量+1=0-0+1+1=2,刚好说明9是8的食物(此时8的绝对偏移量为0+1),同时也是5的食物,而且还是1的天敌,符合题意

  我们最后可以用(delta[x]-delta[y]+d)%3=delta[rootx]的方法来添加绝对偏移量,对于根的绝对偏移量发生变化时,我们可以不必立马对根以下的元素全部进行改变,也改变不了,我们只要查询的时候进行操作再重新记录就可以了

  

 1 #include <iostream>
 2 #include <functional>
 3 
 4 using namespace std;
 5 
 6 typedef int Position;
 7 
 8 Position *pos = NULL;//查并集
 9 Position *delta = NULL;//偏移量集
10 
11 int Find(Position);
12 bool Check(Position, Position, const int);
13 
14 int main(void)
15 {
16     int N, case_sum, ans = 0, inform, x, y;
17     scanf("%d%d", &N, &case_sum);
18     pos = new Position[N + 1];
19     delta = new Position[N + 1];
20     for (int i = 0; i <= N; i++)
21         pos[i] = i;
22     memset(delta, 0, sizeof(Position)*(N + 1));
23 
24     for (int i = 0; i < case_sum; i++)
25     {
26         scanf("%d%d%d", &inform, &x, &y);
27         if (x > N || y > N)
28             ans++;
29         else if (!Check(x, y, inform - 1))
30             ans++;
31     }
32     printf("%d
", ans);
33     delete pos; delete delta;
34     return 0;
35 }
36 
37 int Find(Position x)
38 {
39     //路径压缩
40     if (pos[x] == x)
41         return x;
42     int tmp = Find(pos[x]);
43 
44     delta[x] = (delta[x] + delta[pos[x]]) % 3;
45     pos[x] = tmp;
46     return tmp;
47 }
48 
49 bool Check(Position x, Position y, const int inform)
50 {
51     //设delta[x]为x到根的偏移量
52     Position Rootx, Rooty;
53     Rootx = Find(x);
54     Rooty = Find(y);
55 
56     if (Rootx == Rooty)
57     {
58         if ((delta[y] - delta[x] + 3) % 3 != inform)
59             return false;//如果在同一个集合,不满足偏移,说明出错了
60         else return true;//满足偏移量,则说明正确
61     }
62     //直接合并
63     pos[Rooty] = Rootx;
64     delta[Rooty] = (delta[x] - delta[y] + inform + 3) % 3;
65     return true;
66 }

  

原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/4850887.html