• 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 }

      

  • 相关阅读:
    VSCode拓展插件推荐(HTML、Node、Vue、React开发均适用)
    算法_栈的Java的通用数组实现
    算法_计算输入的算术表达式的值.
    设计模式整理_组合模式
    JavaSE复习_9 集合框架复习
    一个小题目的三种不同的解法
    设计模式整理_状态模式
    设计模式整理_迭代器模式
    设计模式整理_模板模式
    JavaSE复习_8 泛型程序设计
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/4850887.html
Copyright © 2020-2023  润新知