【并查集】NOI2001 食物链 做法详情

目录

注:题目来自ACwing     题目链接点这里!!!

一:题目分析:

二:题目做法

三:分类讨论:

( 1 )X和Y是同类

为什么不更新d[x]:

( 2 ) X吃Y

 四:完整代码

写在最后:


注:题目来自ACwing    题目链接点这里!!!

一:题目分析:

总共有三种动物A,B,C,他们的捕食关系构成一个环

 由于题目是需要判定两种话是否为假话:

        1.X 和 Y 是同类

        2.X 吃 Y

我们可以利用并查集去维护每个动物之间的关系,如果关系与之前的现有的关系有冲突,那么那句话就是假话。

二:题目做法

本题我们利用并查集,所有动物中分为若干个的集合,因为动物只有三种,那么任意两个动物a和b都有如下的三种关系:

        1.两者是同类

        2.a吃b

        3.b吃a

然后根据并查集每个集合都有一个祖宗的结点,我们只需要判定a和祖宗的关系还有b和祖宗的关系,我们就可以得出a和b之间的关系!!!

由于动物与动物之间有三种关系,那么我们可以利用数学中同余的思想:

     就是我们去维护一个distance数组,数组中记录的是集合中的某一个点到祖宗的距离(注意这里的距离并不是指的物理距离,而是与祖宗的关系),那么我们就可以将距离模上3,得到三种不同的结果来代表不同的关系:

1.结果为0,表示同一种类

2.结果为1,表示点x能够吃祖宗类

3.结果为2,表示点x被祖宗类吃,也就是能够吃结果为1的类

如此,在判断任意两个动物的关系的时候,我们只需要判断distance数组的差值模上3的结果,就可以确定两个动物之间的关系。

三:分类讨论:

任意两个动物,可以按照是否在同一个集合,分为两类:

1.两者在同一个集合,那么我们只需要判断距离差值模3的结果就可以得出关系

2.两者在不同的集合,我们可以判断出两者的关系不会存在冲突(因为我们并没有明确的指出每个集合的根节点是A、B、还是C,集合中的关系都只是相对而言的,我们在集合合并的时候,原本未合并之前的集合内部的动物之间的吃与被吃的关系并不会改变),并且因为不同集合的两个点的关系确定了,就可以确定两个集合祖宗的关系,所以我们可以直接合并两集合,并且更新其中一个集合的祖宗到另一个集合祖宗的距离。

题目中有两种询问,分别为X和Y是同类、X吃Y,这里更新距离又可以分为两种情况:

( 1 )X和Y是同类

我们假设px为X的祖宗结点,py为Y的祖宗结点,我们让x的集合归属于y的集合,这里我们简化distance数组为d[];

那么可以得到如下图的关系:

 由于x和y同类,那么x到祖宗(py)的距离减去y到祖宗的距离模3应该等于0.

 我们就完成了距离的更新。

为什么不更新d[x]:

有小伙伴可能会问:为什么只更新了d[px],d[x]表示到祖宗的距离并没有更新:

因为我们在实现并查集查找祖先的函数中同时也将原本x指向其直接父节点编程直接指向祖宗结点

如下图:

实现代码:


int find(int x)
{
    if(p[x]!=x)
    {
        //不能直接p[x]=find(p[x]),因为这样p[x]就被先更新掉了了
        //就不是原来的x结点的直接父节点了,所以要先存起来
        int t=find(p[x]);

        //把x的直接父节点改为祖宗,并且距离为其到原本父节点的距离加上原本父节点到祖宗结点的距离
       
        d[x]+=d[p[x]];
        p[x]=t;
    }
    return p[x];
}

( 2 ) X吃Y

还是这张图,要获得X吃Y的关系,那么就有如下:

 四:完整代码


#include<iostream>
using namespace std;
const int N=50010;
int n,m;

//d[x]存储的就是点x到其直接父节点的距离,p[x]存储的是其直接父节点
int p[N],d[N];

int find(int x)
{
    if(p[x]!=x)
    {
        //不能直接p[x]=find(p[x]),因为这样p[x]就变掉了
        //就不是原来的x结点的直接父节点了,所以要先存起来
        int t=find(p[x]);

        //把x的直接父节点改为祖宗,并且距离为其到原本父节点的距离加上原本父节点到
        //祖宗结点的距离
        d[x]+=d[p[x]];
        p[x]=t;
    }
    return p[x];
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) p[i]=i;
    //因为初始的父节点都是自己,所以d数组初始都为0,不用初始化

    int res=0;//假话的数量
    while(m--)
    {
        int t,x,y;
        cin>>t>>x>>y;
        if(x>n||y>n) res++;//x,y超出范围为假话
        else
        {
            int px=find(x),py=find(y);//分别存储x和y的祖宗
            if(t==1)
            {
                if(px==py&&(d[x]-d[y])%3) res++;//如果在同一个集合,
                                                //并且距离之差不为0说明两者非同类

                else if(px!=py)//不是同一个集合就合并集合并且更新距离
                {
                    p[px]=py;
                    d[px]=d[y]-d[x];//这里画图
                }
            }    
            else
            {
                if(px==py&&(d[x]-d[y]-1)%3) res++;//如果两者在同一个集合并且距离
                                                  //不满足x吃y就是假话
                else if(px!=py)//不是同一个集合就合并集合并且更新距离为x吃y的关系
                {
                    p[px]=py;
                    d[px]=d[y]+1-d[x];
                }

            }
        }

    }
printf("%d",res);




}

写在最后:

              本题就是用距离取模来表示两者之间的关系这一步十分的难以想到(正常人就是想不到),所以没有头绪都是正常的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值