思路分析:

/**

Problem:1182 - 食物链,NOI2001
    Reference:http://apps.hi.baidu.com/share/detail/16059767
    测试数据:
    http://poj.org/showmessage?message_id=93058
    本题思路是带权并查集,我们从最开始讲起。

Part I  - 权值(relation)的确定。
    我们根据题意,森林中有3种动物。A吃B,B吃C,C吃A。
    我们还要使用并查集,那么,我们就以动物之间的关系来作为并查集每个节点的
    权值。
    注意,我们不知道所给的动物(题目说了,输入只给编号)所属的种类。
    所以,我们可以用动物之间“相对”的关系来确定一个并查集。
    0 - 这个节点与它的父节点是同类
    1 - 这个节点被它的父节点吃

2 - 这个节点吃它的父节点。

注意,这个0,1,2所代表的意义不是随便制定的,我们看题目中的要求。
    说话的时候,第一个数字(下文中,设为d)指定了后面两种动物的关系:
    1 - X与Y同类
    2 - X吃Y
    我们注意到,当 d = 1的时候,( d - 1 ) = 0,也就是我们制定的意义
                当 d = 2的时候,( d - 1 ) = 1,代表Y被X吃,也是我们指定的意义。
    所以,这个0,1,2不是随便选的

Part II - 路径压缩,以及节点间关系确定
    确定了权值之后,我们要确定有关的操作。
    我们把所有的动物全初始化。
    struct Animal
    {
        int num; //该节点(node)的编号
        int parent; //该node的父亲
        int relation; //该node与父节点的关系,0同类,1被父节点吃,2吃父节点
    }; Animal ani[50010];
        初始化为
        For i = 0 to N do
            ani[i].num = i;
            ani[i].parent = i;
            ani[i].relation = 0 ; //自己和自己是同类

End For

(1)路径压缩时的节点算法
        我们设A,B,C动物集合如下:(为了以后便于举例)
        A = { 1 , 2 , 3 ,4 ,5 }
        B = { 6 , 7 , 8 ,9 ,10}
        C = { 11, 12, 13,14,15}
        假如我们已经有了一个集合,分别有3个元素
        SET1 = {1,2},我们规定集合中第一个元素为并查集的“代表”
        假如现在有语句:
        2 2 6
        这是一句真话
        2是6的父亲
         ani[6].parent = 2;
         ani[6].relation = 1;
        那么,6和1的关系如何呢?
         ani[2].parent = 1;
         ani[2].relation = 0;
        我们可以发现6与2的关系是 1.
        通过穷举我们可以发现
        ani[now].parent = ani[ani[now].parent].parent;
        ani[now].relation = ( ani[now].relation + ani[now.parent].relation ) % 3;
        这个路径压缩算法是正确的
        关于这个路径压缩算法,还有一点需要注意的地方,我们一会再谈
        注意,根据当前节点的relation和当前节点父节点的relation推出
        当前节点与其父节点的父节点的relation这个公式十分重要!!
        它推不出来下面都理解不了!!自己用穷举法推一下:
        好吧,为了方便伸手党,我给出穷举过程
                i      j
        爷爷  父亲  儿子  儿子与爷爷
               0      0       (i + j)%3 = 0
               0      1       (i + j)%3 = 1
               0      2       (i + j)%3 = 2
               1      0       (i + j)%3 = 1
               1      1       (i + j)%3 = 2
               1      2       (i + j)%3 = 0
               2      0       (i + j)%3 = 2
               2      1       (i + j)%3 = 0
               2      2       (i + j)%3 = 1
        嗯,这样可以看到,( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation
        这就是路径压缩的节点算法
        (2) 集合间关系的确定
        在初始化的时候,我们看到,每个集合都是一个元素,就是他本身。
        这时候,每个集合都是自洽的(集合中每个元素都不违反题目的规定)
        注意,我们使用并查集的目的就是尽量的把路径压缩,使之高度尽量矮
        假设我们已经有一个集合
        set1 = {1,2,7,10}
        set2 = {11,4,8,13},每个编号所属的物种见上文
        set3 = {12,5,4,9}
        现在有一句话
        2 13 2
        这是一句真话,X = 13,Y = 2
        我们要把这两个集合合并成一个集合。
        直接
        int a = findParent(ani[X]);
        int b = findParent(ani[Y]);
        ani[b].parent = a;
        就是把Y所在集合的根节点的父亲设置成X所在集合的根节点。
        但是,但是!!!!
        Y所在集合的根结点与X所在集合的根节点的关系!!!要怎么确定呢?
        我们设X,Y集合都是路径压缩过的,高度只有2层
        我们先给出计算的公式
        ani[b].relation = ( 3 - ani[Y].relation + ( d - 1 ) + ani[X].relation) % 3;
        这个公式,是分三部分,这么推出来的
        第一部分,好理解的一部分:
        ( d - 1 ) :这是X和Y之间的relation,X是Y的父节点时,Y的relation就是这个
        3 - ani[Y].relation = 根据Y与根节点的关系,逆推根节点与Y的关系
        这部分也是穷举法推出来的,我们举例:
        j
        子         父相对于子的relation(即假如子是父的父节点,那么父的relation应该是什么,因为父现在是根节点,所以父.relation = 0,我们只能根据父的子节点反推子跟父节点的关系)
         0             ( 3 - 0 ) % 3 = 0
         1(父吃子)   ( 3 - 1 ) % 3 = 2 //父吃子
         2(子吃父)    ( 3 - 2 ) % 3 = 1 //子吃父,一样的哦亲
        ——————————————————————————————————————————————————————
        我们的过程是这样的:
        把ani[Y],先连接到ani[X]上,再把ani[Y]的根节点移动到ani[X]上,最后,把ani[Y]的根节点移动到ani[X]的根节点上,这样算relation的
        还记得么,如果我们有一个集合,压缩路径的时候父子关系是这么确定的
        ani[爷爷].relation = ( ani[父亲].relation + ani[儿子].relation ) % 3
        我们已知道,( d - 1 )就是X与Y的relation了
        而 (3 - ani[Y].relation)就是 以Y为根节点时,他的父亲的relation
        那么
        我们假设把Y接到X上,也就说,现在X是Y的父亲,Y原来的根节点现在是Y的儿子
          Y的relation   +     ani[Y]根节点相对于ani[Y]的relation
        ( ( d - 1 )         +    ( 3 - ani[Y].relation) ) % 3
        就是ani[Y]的父亲节点与ani[X]的relation了!

那么,不难得到,ani[Y]的根节点与ani[X]根节点的关系是:
        ( ( d - 1 ) + ( 3 - ani[Y].relation) + ani[X].relation ) % 3 ->应用了同余定理
        注意,这个当所有集合都是初始化状态的时候也适用哦
        还是以最开头我们给的三个集合(分别代表三个物种)为例
        2 1 6
        带入公式
        ani[6].relation = ( ( 2 - 1 ) + ( 3 - 0 ) + 0 ) % 3 = 1
        也就是,6被1吃
    Part III - 算法正确性的证明
        首先,两个自洽的集合,合并以后仍然是自洽的
        这个不难想吧,数学上有个什么对称性定理跟他很像的。
        如果理解不了,就这么想!!
        当set1和set2合并之后,set2的根节点得到了自己关于set1根节点的
        正确relation值,变成了set1根节点的儿子,那么
        set2的所有儿子只要用
        ( ani[X].relation + ani[Y].relation ) % 3就能得到自己正确的relation值了
        所以说,针对不在同一集合的两个元素的话,除非违背了(2)和(3),否则永远是真的
        (无论这句话说的是什么,我们都可以根据所给X,Y推出两个子节点之间应有的关系,这个关系一确定,所有儿子的关系都可以确定)

其实所有的不同集合到最后都会被合并成一个集合的。
        我们只要在一个集合中找那些假话就可以了。
        首先,如何判断
        1 X Y是不是假话。//此时 d = 1
        if ( X 和 Y 不在同一集合)
            Union(x,y,xroot,yroot,d)
        else
            if x.relation != y.relation  ->假话
        其次,如何判断
        2 X Y是不是假话 //此时d = 2
        if ( X 和 Y 不在同一集合)
            Union(x,y,xroot,yroot,d)
        else
            (ani[y].relation + 3 - ani[x].relation ) % 3 != 1 ->假话
        这个公式是这么来的:
        3 - ani[x].relation得到了根节点关于x的relation
        ani[y] + 3 - ani[x].relation得到了y关于x的relation
        所以,只要y关于x的relation不是1,就是y不被x吃的话,这句话肯定是假话!

(2)路径压缩要特别注意的一点(错在这里,要检讨自己)
            路径压缩的时候,记得要
            先findParent,再给当前节点的relation赋值。
            否则有可能因为当前节点的父节点的relation不正确而导致错的稀里哗啦。
            例子:
            set1 = {1,2,7,10}
            set2 = {3,4,8,11}
            set3 = {12,5,14,9}
            Union(1,3,1,3,1)
            Union(3,12,3,12,2)
            1 5 1
            算5的relation
            如果不先更新parent的relation,算出来应该是
            ( 3 - 0 + 0 + 1 ) % 3 = 1,5被1吃,显然不对
            这里面,+ 0的那个0是指根节点 12 的relation(未更新,这里的0是指12与11的relation)
            如果更新完了的话,应该是
            ( 3 - 0 + 2 + 1 ) % 3 = 0 ,5与1是同一物种,对了
            这里面的 2 是更新节点12的relation(12与1的relation)

*/

关于此题的进一步的总结:

输入:动物个数n以及k句话,接着输入k行,每一行形式为:d x y,
   在输入时可以先判断题目所说的条件2和3,即:
       1>若(x>n||y>n):即当前的话中x或y比n大,则假话数目num加1.
       2>若(x==2&&x==y):即当前的话表示x吃x,则假话数目num加1.
   而不属于这两种情况外的话语要利用并查集进行判断当前的话是否与此前已经说过的话相冲突.
   struct node
   {
       int parent;                     //p[i].parent表示节点i的父节点
       int relation;                   //p[i].relation表示节点i与其父节点(即p[i].parent)的关系
   }p[50005];
   此处relation有三种取值(假设节点x的父节点为rootx,即p[x].parent=rootx):
   p[x].relation=0   ……》表示节点x与其父节点rootx的关系为同类
   p[x].relation=1   ……》表示节点x的父亲节点rootx吃节点x,即节点x被吃
   p[x].relation=2   ……》表示节点x吃它的父亲节点rootx,即节点x吃
   
   初始化函数为:void init_set(int n)
               {
                   int i;
                   for(i=1;i<=n;i++)
                   {
                       p[i].parent=i;            //初始时集合编号就设置为自身
                       p[i].relation=0;        //因为p[i].parent=i,即节点i的父亲节点就是自身,所以此时节点i与其父亲节点的关系为同类(即p[i].relation=0)
                   }
               }

下面为并查集的两个重要操作:查找和合并.
   在查找时因为节点不仅有父亲节点域,而且还有表示节点与其父亲节点关系的域,查找过程中对父亲节点域的处理和平常的处理一样,即在查找过程中同时实现路径 压缩,但正是由于路径压缩,使得表示节点与其父亲节点的关系的域发生变化,所以相应查找节点表示其与父亲节点的关系的域也要发生变化(因为路径压缩之前节 点x的父亲节点为rootx的话,那么在路径压缩之后节点x的父亲节点就变为了节点rootx的父亲节点rootxx,所以此时 p[x].relation存储的是节点x与现在父亲节点rootxx的关系),此处可以画图理解一下:

很明显查找之前节点x的父亲节点为rootx,假设此时p[x].relation=1(即表示x的父亲节点rootx吃x)且 p[rootx].relation=0(即表示rootx和其父亲节点rootxx是同类),由这两个关系可以推出rootxx吃x,而合并以后节点x 的父亲节点为rootxx(实现了路径压缩),且节点x的父亲节点rootxx吃x,即查找之后p[x].relation=1,很容易的推出合并之后的 p[x].relation=(p[rootx].relation+p[x].relation)%3.

在将元素x与y所在的集合合并时,假设元素x所在的集合编号为rootx,元素y所在的集合编号为rooty,合并时直接将集合rooty挂到集合 rootx上,即p[rooty].parent=rootx,此时原来集合rooty中的根节点rooty的relation域也应随之发生变化,因为 合并之前rooty的父亲节点就是其自身,故此时p[rooty].relation=0,而合并之后rooty的父亲节点为rootx,所以此时需判断 rootx与rooty的关系,即更新p[rooty]的值,同理画图理解:
     
   此时假设假设p[x].relation=0(即x与rootx的关系是同类),p[y].relation=1(即rooty吃y),则有:
        1>输入d=1时,即输入的x和y是同类,则有上述关系可以推出rooty吃rootx,即p[rooty].relation=2;
        2>输入d=2时,即输入的x吃y,则有上述关系可以推出rooty与rootx是同类(因为rooty吃y,x吃y,则rooty与x是同类,又rootx与x是同类),即p[rooty].relation=0;
   当然,这只是一种可能,其它的可能情况和上面一样分析,则可以得知:p[rooty].relation=(3+(d-1)+p[x].relation-p[y].relation)%3.

当元素x与元素y在同一集合时,则不需要合并,因为此时x与y的父亲节点相同,可以分情况讨论:
        1>d=1时,即x与y是同类时,此时要满足这要求,则必须满足p[x].relation=p[y].relation,这很容易推出来.
        2>d=2时,即表示x吃y,此时要满足这要求,则必须满足(p[y].relation-p[x].relation+3)%3=1,如x和 root是同类(即p[x].relation=0),此时要满足x吃y,则必须满足root吃y,即p[y].relation=1,可以像上面一样画 图来帮助理解.
   这道题目还有一点需要注意:输入的只有一组数据,若输入多组数据就显示Wrong Answer.
#include<iostream>
#include<cstdlib>
#include<string>
#include<algorithm>
using namespace std;

struct node
{
       int parent;
       int relation;
}p[50005];

void init_set(int n)                                                                      //初始化并查集
{
     int i;
     for(i=1;i<=n;i++)
     {
          p[i].parent=i;
          p[i].relation=0;
     }
}

int find(int x)                                                                       //查找元素x所在的集合
{
    int temp;
    if(x==p[x].parent)                                                            //x所在集合的根节点就是它自身
       return x;
    temp=p[x].parent;
    p[x].parent=find(temp);                                                   //路径压缩
    p[x].relation=(p[temp].relation+p[x].relation)%3;             //更新压缩后的关系域
    return p[x].parent;                                                           //返回所在的集合编号
}

void union_set(int x,int y,int rootx,int rooty,int d)     //将元素x与元素y所在的集合合并,rootx,rooty分别为x,y所在集合的根节点,d为题目所表示的关系(1和2)
{
     p[rooty].parent=rootx;                                                           //合并集合
     p[rooty].relation=(3+(d-1)+p[x].relation-p[y].relation)%3;    //更新原来集合rooty的关系域
}

int main()
{
    int n,k,x,y,d,i,px,py,num=0;
    scanf("%d%d",&n,&k);
    init_set(n);
    while(k--)
    {
                 scanf("%d%d%d",&d,&x,&y);
                 if(x>n||y>n)                                      //x或y大于n则就是假话
                   num++;
                 else if(d==2&&x==y)                       //此时表示x吃x
                   num++;
                 else
                 {
                     px=find(x);
                     py=find(y);
                     if(px!=py)                                  //x与y不在同一集合就进行合并
                        union_set(x,y,px,py,d);
                     else                                           //否则按上述所说进行处理
                     {
                         if(d==1&&p[x].relation!=p[y].relation)
                            num++;
                         else if(d==2&&(p[y].relation-p[x].relation+3)%3!=1)
                            num++;
                     }
                 }
    }
    printf("%d\n",num);
    system("pause");
    return 0;
}

【POJ1182】食物链+思路+代码(较全)相关推荐

  1. 图片高亮处理编程_GMT语法高亮-智能提示-代码补全插件

    GMT(Generic Mappint Tools)是地学界应用非常广泛的一款绘图兼数据处理的开源软件.其开发团队也是非常活跃,此软件还在不断的发展和更新中,变得越来越强大.目前已经有164个模块,而 ...

  2. Bailian1182 POJ1182 食物链【并查集】

    1182:食物链 描述 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到 ...

  3. php强类型 vscode,VSCode 扩展入门,后缀代码补全的实现

    我们项目组使用了 protobuf 作为传输协议,其好处不用多说.可是由于 go 默认数字类型是 int,且 go 属于强类型语言,切换类型就成了家常便饭. 一般来说,切换类型的步骤不外乎: 选中数字 ...

  4. Leetcode各种题型题目+思路+代码(共176道题)

    文章目录 第一章:Leetcode 每日很多题 1.Leetcode-1047 删除字符串中的所有相邻重复项 2.剑指 Offer 53 - I. 在排序数组中查找数字 I 3.Leetcode704 ...

  5. 【第十一届泰迪杯数据挖掘挑战赛】A 题:新冠疫情防控数据的分析 思路+代码(持续更新)

    [第十一届泰迪杯数据挖掘挑战赛]A 题:新冠疫情防控数据的分析 思路+代码(持续更新) 问题背景 解决问题 代码下载 数据分析 Task1 Task2 Task 3 问题背景 自 2019 年底至今, ...

  6. uni-app 接入银联H5支付(Java)思路代码

    uni-app 接入银联H5支付(Java)思路代码 uni-app-web-view Java(后台部分代码)可以参考官方的Demo取代码 银联文档地址: H5支付. 第一次接触银联支付的话建议 跑 ...

  7. vim+设置php+高亮,VIM语法高亮、VIM代码补全、VIM结构化视图功能的配置实现

    =========================================== [简介] 为了更加方便的学习和研究一个开源c++项目(当然linux环境下),特地花了一两周的时间研究vim及其 ...

  8. scala入门之代码补全

    为什么80%的码农都做不了架构师?>>>    在scala的shell命令行中,我们可以使用像Linux那样的代码补全功能.Linux中是使用Tab键补全,scala的shell命 ...

  9. 几行代码构建全功能的对象检测模型,他是如何做到的?

    作者 | Alan Bi 译者 | 武明利,责编 | Carol 出品 | AI科技大本营(ID:rgznai100) 如今,机器学习和计算机视觉已成为一种热潮.我们都看过关于自动驾驶汽车和面部识别的 ...

最新文章

  1. Linux 黑话解释:什么是定时任务
  2. 模拟电路推荐学习书单
  3. 异常:由于代码已经过优化或者本机框架位于调用堆栈之上,无法计算表达式的值...
  4. HDU 2255 二分图最佳匹配 模板题
  5. title()、upper()、lower()的用法
  6. 物联网培训总结(C#)
  7. 【已解决】华硕电脑关闭触摸板,安装完触摸板驱动,Fn+F9仍然无法解决问题
  8. 重磅!腾讯优图11篇论文入选AI顶会AAAI 2021
  9. 数据结构21:递归可视化(谢尔宾斯基三角形)
  10. 树莓派控制超声波测距原理
  11. mac安装win10_老笔记本加装1T固态硬盘,顺便安装win10 Mac双系统,真香
  12. 【软件入门】Typora快速入门
  13. 非递归式查找树形数据
  14. Dialog 无法隐藏软键盘
  15. 【Unity游戏开发笔记】手游-涂鸦弹跳开发分析
  16. 月薪超1.6万美元!对冲基金实习生也内卷
  17. linux内核进程cmd,linux – 如何在内核模块中使用proc_pid_cmdline
  18. 联想笔记本ubuntu系统下的背光调节
  19. shell脚本1例 自动安装httpd
  20. 新一代大数据任务调度 - Apache DolphinScheduler喜提十大开源新锐项目 最具人气项目

热门文章

  1. 【全流程】从头在树莓派4B上部署自己训练的yolov5模型(配合NCS2加速)
  2. Asp.Net 简繁转换
  3. 基于采样的规划算法之动态窗口法(DWA)
  4. android布局闪动,设置child.setvisibility(View.Visible)时,Android主屏幕出现效果闪烁问题...
  5. windows上gn编译指引
  6. Linux常用工具使用手册——文件MD5的验证方法
  7. pytorch学习(五)---torch.nn模块
  8. 中国大学MOOC课程信息之数据分析可视化二
  9. 北京市政交通一卡通余额提醒
  10. Base-N 算法加密解密实现: