题目描述
动物王国中有三类动物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句话有的是真的,有的是假的。
当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N和K句话,输出假话的总数。
输入样例
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出样例
3
算法
(并查集)
我们可以把每个动物看作一个节点,因为我们知道一个动物的物种,我们就可以通过它推出其他物种。
比如一条长为4的链 Player1 -> Player2 -> Player3 -> Player4 (A -> B的意思是B吃A) ,我们知道Player3是A物种。那么Player2一定是B物种,因为A只吃B物种,不吃C物种或是自己的同类。同理可得Player1是C物种,Player4也是C物种
所以,每一条像这样的链上都是A、B、C这三种物种循环,我们可以通过这个来辨别真假话。
定义数组d[i]表示节点i到root节点距离%3的结果帮助解题。(在上述例子中,也就是Player2~4的的d[i]都是到Player1的距离)
下面我们开始考虑如何分辨真假话:
1.X和Y中有数大于N,则该句是假话
2.当读入说X,Y是同种物种时,如果他们不在一个并查集中,那这句话肯定对;否则,如果他们到root节点的距离一样,则是真话,要不然就是假话。
3.当读入说X,Y是异种物种时,方法同2
当我们发现他们不在一个并查集时,我们需要将它们合并
d[f[x]] = (d[y] - d[x] + 3) % 3 (异种物种应改为+ 4)
f[f[x]] = f[y]
C++ 代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 100005;
int n, k, f[N], d[N], ans;
int find(int x)
{
if (x != f[x])
{
int tmp = f[x];
f[x] = find(f[x]);
d[x] = (d[x] + d[tmp]) % 3;
}
return f[x];
}
int main()
{
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= k; i++)
{
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
if (b > n || c > n)
{
ans++;
continue;
}
int fab = find(b), fac = find(c);
if (a == 1)
if (fab == fac)
{
if (d[b] != d[c])
ans++;
continue;
}
else
{
d[f[b]] = (d[c] - d[b] + 3) % 3;
f[f[b]] = f[c];
}
if (a == 2)
{
if (fab == fac)
{
if (d[b] != (d[c] + 1) % 3)
ans++;
continue;
}
else
{
d[f[b]] = (d[c] - d[b] + 4) % 3;
f[f[b]] = f[c];
}
}
}
printf("%d\n", ans);
return 0;
}