洛谷 P2024 食物链 (种类并查集)

动物王国中有三类动物 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 句话有的是真

的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

• 当前的话与前面的某些真的话冲突,就是假话

• 当前的话中 X 或 Y 比 N 大,就是假话

• 当前的话表示 X 吃 X,就是假话

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入输出格式

输入格式:

 

从 eat.in 中输入数据

第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

 

输出格式:

 

输出到 eat.out 中

一行,一个整数,表示假话的总数。

 

输入输出样例

输入样例#1: 复制

100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5

输出样例#1: 复制

3

说明

1 ≤ N ≤ 5 ∗ 10^4

1 ≤ K ≤ 10^5

 

解题思路:除了原来的f数组,我们新增一个d数组,用0 1 2 分别表示当前节点被父节点吃 跟父节点同类和吃父节点。

在路径压缩的时候就能发现子节点与父节点存在一个%3的关系。另外需要注意的就是节点合并的时候需要分情况讨论父节点的关系。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define sca(x) scanf("%d",&x)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define inf 0x3f3f3f3f
#define mod 1000000007
#define LL long long
#define N 50005
int f[N],r[N];

int  F(int x)
{
    if(f[x]==x) return x;
    int fa=f[x];
    f[x]=F(f[x]);
    r[x]=(r[x]+r[fa])%3;
    return f[x];
}

int U(int ff,int x,int y)
{
    int f1=F(x);
    int f2=F(y);
    if(ff==1)
    {
        if(f1==f2)
        {
            if(r[x]!=r[y])return 0;
            return 1;
        }
        else
        {
            f[f1]=f2;
            if(r[x]==0)r[f1]=r[y];
            else if(r[x]==1&&r[y]==1)r[f1]=0;
            else if(r[x]==1&&r[y]==0)r[f1]=2;
            else if(r[x]==1&&r[y]==2)r[f1]=1;
            else if(r[x]==2&&r[y]==0)r[f1]=1;
            else if(r[x]==2&&r[y]==1)r[f1]=2;
            else if(r[x]==2&&r[y]==2)r[f1]=0;
            return 1;
        }
    }
    if(ff==2)
    {
        if(f1==f2)
        {
            if(r[x]==0&&r[y]!=1)return 0;
            if(r[x]==1&&r[y]!=2)return 0;
            if(r[x]==2&&r[y]!=0)return 0;
            else f[f1]=f2;
            return 1;
        }
        else
        {
            f[f1]=f2;
            if(r[x]==0&&r[y]==0)r[f1]=2;
            else if(r[x]==0&&r[y]==1)r[f1]=0;
            else if(r[x]==0&&r[y]==2)r[f1]=1;
            else if(r[x]==1&&r[y]==0)r[f1]=1;
            else if(r[x]==1&&r[y]==1)r[f1]=2;
            else if(r[x]==1&&r[y]==2)r[f1]=0;
            else if(r[x]==2&&r[y]==0)r[f1]=0;
            else if(r[x]==2&&r[y]==1)r[f1]=1;
            else if(r[x]==2&&r[y]==2)r[f1]=2;
        }
        return 1;
    }
    return 1;
}


int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    int ff,x,y;
    int sum=0;
    for(int i=1;i<=n;i++)f[i]=i,r[i]=0;
    while(k--)
    {
        scanf("%d%d%d",&ff,&x,&y);
        if(x>n||y>n)
        {
            sum++;
            continue;
        }
        if(ff==2&&x==y){
            sum++;
           continue;
        }
        if (!U(ff,x,y))
        {
            sum++;
        }
    }
    cout<<sum<<endl;
}

上边的知识为了理解一般并查集中间的过程,代码比较长且需要重复的分情况讨论。

后来学习了一个简单的方法就是开三倍的数组空间维护的并查集 一倍存自己 2被存自己的吃的对象 3被存天敌;

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define sca(x) scanf("%d",&x)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define inf 0x3f3f3f3f
#define mod 1000000007
#define LL long long
#define N 50005
int f[N*3];
int F(int x)
{
    return f[x]==x?x:f[x]=F(f[x]);
}
int main()
{
    int n,k;
    sca(n),sca(k);
    rep(i,1,3*n)f[i]=i;
    int op,u,v,ans=0;
    rep(i,1,k)
    {
        scanf("%d%d%d",&op,&u,&v);
        if(u>n||v>n)
        {
            ans++;
            continue;
        }
        if(op==2&&u==v)
        {
            ans++;
            continue;
        }
        if(op==1)
        {
            if(F(u)==F(v+n)||F(v)==F(u+n))ans++;//如果u吃v或者v吃u
            else
            {
                rep(j,0,2)f[F(u+n*j)]=F(v+n*j);//合并
            }
        }
        if(op==2)
        {
            if(F(u)==F(v)||F(v)==F(u+n))ans++;//如果u与v同类或者v吃u
            else
            {
                f[F(u)]=F(v+n);
                f[F(u+n)]=F(v+2*n);
                f[F(u+2*n)]=F(v);
            }
        }
    }
    cout<<ans<<endl;
}

基于第一种做法,有一个利用向量思想快速确定两个 父亲节点的关系的方法。

例如:

 

如图利用向量的思想我们知道x与f2的关系为(op+d[y])%3=d[x]+d[f1]

所以d[f1]=(3-d[x]+d[y]+(x与y的关系))%3;

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define sca(x) scanf("%d",&x)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define inf 0x3f3f3f3f
#define mod 1000000007
#define LL long long
#define N 50005
int f[N];
int d[N];
int F(int x)
{
    if(f[x]==x)return x;
    int fa=f[x];
    f[x]=F(f[x]);
    d[x]=(d[x]+d[fa])%3;
    return f[x];
}

int U(int op,int x,int y)
{
    int f1=F(x);
    int f2=F(y);
    if(op==1)
    {
        if(f1==f2&&d[x]!=d[y])return 1;
        else
        {
            f[f1]=f2;
            d[f1]=(3-d[x]+d[y])%3;
            return 0;
        }
    }
    if(op==2)
    {
        if(f1==f2)
        {
            if(d[x]!=(d[y]+1)%3)return 1;
            else return 0;
        }
        else
        {
            f[f1]=f2;
            d[f1]=(3-d[x]+d[y]+1)%3;
            return 0;
        }
    }
    return 0;

}
int main()
{
    int n,k;
    sca(n),sca(k);
    rep(i,1,n)f[i]=i,d[i]=0;
    int op,u,v,ans=0;
    rep(i,1,k)
    {
        scanf("%d%d%d",&op,&u,&v);
        if(u>n||v>n)
        {
            ans++;
            continue;
        }
        if(op==2&&u==v)
        {
            ans++;
            continue;
        }

        ans+=U(op,u,v);
    }
    cout<<ans<<endl;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值