洛谷 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

分析

1.p[x]表示x根结点。r[x]表示p[x]与x关系。r[x]=0 表示p[x]与x同类;1表示p[x]吃x;2表示x吃p[x]。
2.怎样划分一个集合呢?
注意,这里不是根据x与p[x]是否是同类来划分。而是根据“x与p[x]能否确定两者之间关系”来划分,若能确定x与p[x]关系,则它们同属一个集合
3.怎样判断一句话是不是假话?
假设已读入D ,X ,Y ,先利用find()函数得到X,Y所在集合代表元素fx,fy,若它们在同一集合(即fx==fy)则可以判断这句话真伪:
若 D==1 而 r[X]!=r[Y] 则此话为假.(D==1 表示X与Y为同类,而从r[X]!=r[Y]可以推出 X 与 Y 不同类.矛盾.)
若 D==2 而 r[X]==r[Y](X与Y为同类)或者r[X]==(r[Y]+1)%3(Y吃X)则此话为假。
4.上个问题中r[X]==(r[Y]+1)%3这个式子怎样推来?
假设有Y吃X,那么r[X]和r[Y]值是怎样?
我们来列举一下:

           r[X]=0&&r[Y]=2 

                   r[X]=1&&r[Y]=0

                   r[X]=2&&r[Y]=1

稍微观察一下就知道r[X]=(r[Y]+1)%3;
事实上,对于上个问题有更一般判断方法:
若(r[Y]-r[X]+3)%3!=D-1 ,则此话为假.
5.其他注意事项:
在unionn(d,x,y)过程中若将S(fy)合并到S(fx)上,则相应r[fy]必须更新为fy相对于fx关系。怎样得到更新关系式?
r[fy]=(r[x]-r[y]+d+3)%3;

代码

#include <bits/stdc++.h>

#define N 50001

using namespace std;
int p[N],r[N];
int n,k,ans,x,y,d;

int find(int x)
{
    if (x!=p[x])
    {
        int fx=find(p[x]);
        r[x]=(r[x]+r[p[x]])%3;
        p[x]=fx;
    }
    return p[x];
}

bool unionn(int d,int x,int y)
{
    int fx=find(x),fy=find(y);
    if (fx==fy)
        if ((r[y]-r[x]+3)%3!=d) return 1;
        else return 0;
    p[fy]=fx;
    r[fy]=(r[x]-r[y]+d+3)%3;
    return 0;
}

main()
{
    scanf("%d %d",&n,&k);
    for (int i=1;i<=n;i++) 
        p[i]=i,r[i]=0;
    for (int i=1;i<=k;i++)
    {
        scanf("%d %d %d",&d,&x,&y);
        if(x>n||y>n||(x==y && d==2))
        {
            ans++;
            continue;
        }
        if (unionn(d-1,x,y)) ans++;
    }
    printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值