题述
有N只动物,分别编号为1,2,…,N-1。所有动物都属于A,B,C中的一种。已知A吃B、B吃C、C吃A。按顺序给出下面的两种信息共K条。
第一种:x和y属于同一种类。
第二种:x吃y。
然而这些信息有可能会出错。有可能有的信息和之前给出的信息矛盾,也有的信息可能给出的x和y不在1,2,…,N的范围内。求在K条信息中有多少条是不正确的。计算过程中,我们将忽视诸如此类的错误信息。
限制条件
1<=N<=50000
0<=K<=100000
样例
输入
100 7( N K)
1 101 1(T[i] X[i] Y[i])
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出
3
题记
我们并不需要知道每个动物是那种,只需要找矛盾,后面的只要跟前面的矛盾,后面的就是错的。对于每只动物i创建出来3个元素i-A,i-B,i-C,并用这3*N个元素建立并查集。这个并查集维护如下信息:
i-x表示i属于种类x
并查集里的每一组表示组内所有元素代表的情况都同时发生或不发生
例如,如果i-A和j-B在同一个组里,就表示如果i属于种类A那么一定就有j属于B,如果j属于种类B那么就一定有i属于种类A。因此对于每一条信息,只需要按照下面进行操作就可以了
第一种,x和y属于同一种类 合并x-A,y-A、x-B,y-B、x-C、y-C
第二种,x吃y,合并x-A,y-B、x-B,y-C、x-C、y-A
代码如下
#include <iostream>
//食物链(并查集)
using namespace std;
const int Maxn=50005;
const int MaxK=100005;
int par[Maxn]; //父亲
int tree_rank[Maxn]; //树的高度
//输入
int N,K;
int T[MaxK],X[MaxK],Y[MaxK]; //T是信息类型
//初始化n个元素
void init(int n){
for(int i=0;i<n;i++){
par[i]=i;
tree_rank[i]=0;
}
}
//查询树的根
int find(int x){
if(par[x]==x)
return x;
else
return par[x]=find(par[x]); //查询过程中经过的所有点都改为直接连到根上
}
//合并x和y所属的集合
void unite(int x,int y){
x=find(x);
y=find(y);
if(x==y)
return;
if(tree_rank[x]<tree_rank[y]){
par[x]=y;
}
else{
par[y]=x;
if(tree_rank[x]==tree_rank[y])
tree_rank[x]++;
}
}
//判断x和y是否属于同一个集合
bool same(int x,int y){
return find(x)==find(y);
}
void solve(){
//初始化并查集
//元素x,x+N,x+2*N分别表示x-A,x-B,x-C
init(3*N);
int ans=0;
for(int i=0;i<K;i++){
int t=T[i];
int x=X[i]-1,y=Y[i]-1; //把输入变成0,1,2,...,N-1
//不正确的编号
if(x<0||N<=x||y<0||N<=y){
ans++;
continue;
}
if(t==1){
//x和y属于同一类
if(same(x,y+N)||same(x,y+2*N))
ans++;
else{
unite(x,y);
unite(x+N,y+N);
unite(x+2*N,y+2*N);
}
}
else{
//x吃y
if(same(x,y)||same(x,y+2*N))
ans++;
else{
unite(x,y+N);
unite(x+N,y+2*N);
unite(x+2*N,y);
}
}
}
printf("%d\n",ans);
}
int main()
{
scanf("%d %d",&N,&K);
for(int i=0;i<K;i++)
scanf("%d %d %d",&T[i],&X[i],&Y[i]);
solve();
return 0;
}