动物王国中有三类动物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(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
Sample Input
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5
Sample Output
3
题意:题目告诉有 3 种动物,互相吃与被吃,现在告诉你 m 句话,其中有真有假,叫你判断假的个数 ( 如果前面没有与当前话冲突的,即认为其为真话 )。每句话开始都有三个数 D A B,当D = 1时,表示A 和B是同类,当D = 2时表示A 吃 B。
分析:既然要求假话,肯定是说的话跟以前说的存在矛盾,比如A->B,B->A,这样很明显第二句话是假话了,跟第一句话冲突,那么我们应该怎么去查询这种冲突呢,我们知道查询两个节点之间关系最快的方法就是并查集,只要把一些节点归为一个集合,查询起来是非常方便的,那么这道题是不是也可以这么做呢?答案是肯定的,不过我们除了要记录每个节点的根节点以外,还要记录与根节点的关系
而我们要归为一个集合的条件是看看这个节点 存不存关系, 先规定生物之间的关系, 0 表示二者同类, 1 表示A吃B,2表示B吃A;
说假话的条件:
1- 直接说假话,A或者B 的值大于动物的总数,(很明显是存在这种可能的,AB的取值范围大于动物总数),或者当D = 2, 时候 A == B,这也是一个睁眼说的假话,(这是同类相食,我们不允许这样的事情发生,所以这也是假话)。
2 - 间接说假话,间接说假话就是跟以前说的话有冲突,我们先查找A和B的根节点为 ra 和 rb 如果ra == rb(根节点相同,说明二者属于同一个集合,说明前面的话中二者存在 上面的三种关系之一,只需判断当前这句话,和前面话中的关系所说是否一样,不一样就是假话,一样就是真话); 当ra 和 rb 不同时,说明 A和B 以前不存在关系,让他们两个所属集合合并就行,让他们两个存在关系就行了;
看关系的推导是不是和和向量关系一样
大家看看这个是不是特别像什么东西??对了,就是向量,如果我们把这个转换成向量求是不是也可以呢???这是一个美好的想法,我们可以验证一下。
很明显A->C = (A->B+B->C) % 3
这也恰好是符合向量的运算,那么相同一棵树的节点间的关系就解决了。
下面是路径压缩 和 合并;在这里就是幷查集的知识了,运用自己到根节点的关系 进行路径压缩 和 合并,下面ra 和 rb 是a,b的根节点;
当给出 a和b已经存在关系了,也就是ra==rb ,他们在一个集合中,判断当前给出的关系是不是 和 以前的推导出的关系一样;
只需判断 a->b + b->rb 是不是等于 a->ra 就行了(ra==rb)若一样就是真话,若不一样就是假话;
当 a和b以前不存在关系时,也就是 ra!= rb;这时候需要合并,把根节点进行合并,根节点之间存在关系了,那么就成一颗树了,这棵树中任意两个节点都存在关系;
已知 a->b、 b->rb 、a->ra 之间的关系,求 ra ->rb的关系;
ra->rb = ra->b + b -> rb;
ra->b = a->b - a->ra
两式合并
ra->rb = a->b - a->ra + b->rb;
代码:
#include<stdio.h>
//#include<bits/stdc++.h>
using namespace std;
#define Max 50005
int f[Max],r[Max]; //r[x] 存 x->f[x]的关系;
int n,k;
void init()
{
for(int i = 0;i<=n;i++)
f[i] = i,r[i] = 0;
}
int find(int u)
{
int k = f[u];
if(f[u]==u)
return u;
else
{
f[u] = find(f[u]);
r[u] = (r[u] + r[k])%3; //回溯时 u->root = u-> f[u] + f[u]->root;
// 都是对根节点所说的,都是路径压缩;
return f[u];
}
}
int main()
{
scanf("%d%d",&n,&k);
init();
int d,x,y;
int cnt = 0;
// 当根节点相同时,说明现在的x,y 存在上述关系0,1,2 中的一种关系,只需看看这句话和
// 当前所存在的关系相不相符;不相符就是假话;相符就是真话;
// 当根节点不相同时,说明现在的x,y还没有关系,需要合并关系;
while(k --)
{
scanf("%d%d%d",&d,&x,&y);
int rx = find(x),ry = find(y);
if(x>n||y>n||(d==2&&x==y))
cnt ++;
else if(rx==ry&&((d-1)+r[y])%3!=r[x])
cnt ++;
else if(rx!=ry)
{
f[rx] = ry;
r[rx] = ((d-1)-r[x]+r[y]+3)%3; // 中间有减号,可能为负,所以先加上3,再对3取余
}
}
printf("%d\n",cnt);
return 0;
}