B. 食物链【NOI2001】
内存限制:256 MiB
时间限制:1000 ms
标准输入输出
题目类型:传统
评测方式:文本比较
题目描述
动物王国中有三类动物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),输出假话的总数。
输入格式
第一行是两个整数N和K,以一个空格分隔。以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
输出格式
只有一个整数,表示假话的数目。样例
样例输入
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
样例输出
3
数据范围与提示
100 7 1 101 1 假话 2 1 2 真话 2 2 3 真话 2 3 3 假话 1 1 3 假话 2 3 1 真话 1 5 5 真话
首先考虑题目中所说的3种矛盾情况中的后两种,都很好判断,关键是如何判断当前的话是与前面的话冲突的。
这里我们先给出两个定理以方便判断一些关系:
一.因为三个种族都有一个且且仅有一个能吃的种族。如果动物A能吃B,动物C也能吃B,那么说明 A和C便是同一个种族 ,否则这个关系就是矛盾的。
二.如果A能吃B,B能吃C,那么可以得出C能吃A,也就是如下这个关系:
对于单个点(A_{1}),我们可以给它建两个虚点(A_{2},A_{3}),并且假设出这三个点之间的关系,(A_{1})吃(A_{2}),(A_{2})吃(A_{3}),(A_{3})吃(A_{1})。同样在假设有一个点(B_{1}),它同样也有这些虚点。我们可以使用一个有向图来表示这个关系以方便理解。
当(A_{1})可以吃掉(B_{1})时,我们可以发现(A_{1})和(B_{3})成了同类(参考定理1,同样根据定理1,我们还可以得出(A_{2})和(B_{1})是同类,如果是(B_{1}吃A_{1}则相反))。
根据定理2,我们可以得出(B_{1})可以吃掉(A_{3}),同时它也可以吃掉(B_{2}),所以(A_{3})和(B_{2})也是同类..
如果我们判断两个点是同类,则可以将他们放入同一个并查集中。需要注意的是,虚点只是用来帮助我们判断动物之间的关系的,并没有实际含义。
那么如何判断一句话是矛盾的呢,我们可以分情况讨论。
如果(A_{1})可以吃(B_{1}),结合图片,我们可以发现(A_{1})和(B_{2}),依此类推(B_{1})和(A_{3})也不是同类,(A_{1})和(B_{1})更不是同类。我们便可以使用并查集,如果上述两者在同一个并查集,便说明它们是矛盾的。
同理如果(A_{1})和(B_{1})是同类的话也可以这么考虑。需要注意的是,所以情况都要考虑完,实点和虚点入并查集时情况也要考虑完(不然就是WA,XD)。
可能有些同学要问为什么要判断两个点在同一个并查集,来判断这句话是不是矛盾的,而不是判断两个点不在同一个并查集,也就是
if(find(A1) == find(B2)) 矛盾
和
if(find(A3) != find(B2)) 矛盾
的区别。
因为我们建立的是虚点,一开始所有的点都在独立的并查集,直接判断便会误判。
可以这么理解,我们在条件不够的情况下,第二种方式是“猜测”,第一种方式则是“尽量满足关系,最后不得不判断矛盾”。
关于建立虚点,设当前有n个实点,(A)的两个虚点可以用(A+n)和(A+2*n)来表示.
代码
#include <iostream>
#include <cstdio>
#include <string>
#include <map>
using namespace std;
#define N 50010
int fa[N*3],n,k,ans;
int find(int x) {
if(fa[x]!=x) return fa[x]=find(fa[x]);
return x;
}
int main() {
cin>>n>>k;
for(int i=1;i<=n*3;i++) fa[i]=i;
for(int i=1,a,b,c;i<=k;i++) {
cin>>a>>b>>c;
if(b>n || c>n || (a==2 && b==c)) { ans++; continue; }
if(a==2) {
if(find(b)==find(c)||find(b+n*2)==find(c)||find(c+n)==find(b)) ans++;
else {
fa[find(b+n)]=find(c);
fa[find(b)]=find(c+2*n);
fa[find(c+n)]=find(b+2*n);
}
}
if(a==1) {
if((find(b+n) == find(c)) ||(find(b+2*n) == find(c))) ans++;
else {
fa[find(b+2*n)]=find(c+2*n);
fa[find(b+n)]=find(c+n);
fa[find(b)]=find(c);
}
}
}
cout<<ans;
}