POJ1182
Description
动物王国中有三类动物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
分析:
已知的是动物之间的关系,判断当前关系和之前关系是否冲突,本质上就是利用并查集维护已知的关系,通过find()来判断新的关系是否与已知相矛盾。
方法1
如图,对每个动物建立三个节点(动物i有3i,3i+1,3i+2这三个节点),分别表示与该动物同类的生物,该动物吃的生物,吃该动物的生物。每次建立新关系时需要维护六个节点之间的关系。
如果条件为2 1 2,1 2 3就利用并查集建立如下的关系:
代码:
#include <iostream>
#include <cstdio>
using namespace std;
int fa[160000];//一个动物占用三个节点,i号动物的父节点是fa[3*i]
bool vis[160000]={0};
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main()
{
int n,k,d,x,y;
int i;
int res=0,px,py;
cin>>n>>k;
for(i=0;i<3*n;i++)//初始化各节点指向自己
fa[i]=i;
while(k--){
scanf("%d%d%d",&d,&x,&y);
if (x > n || y > n) { res++; continue; }
if (d==2 && x==y) { res++; continue; }
px=find(3*x);py=find(3*y);//px,py可能不是3的倍数;
int tx=px-px%3;//tx时px/3生物的1节点;
int ty=py-py%3;//ty时py/3生物的1节点;
if(vis[x]==0 || vis[y]==0){//有一个是未知生物,直接加入并查集
vis[x]=1;vis[y]=1;
if(d==1){
fa[px]=py;fa[tx+(px+1)%3]=ty+(py+1)%3;fa[tx+(px+2)%3]=ty+(py+2)%3;
}
else{//x吃y
fa[px]=ty+(py+2)%3;fa[tx+(px+1)%3]=py;fa[tx+(px+2)%3]=ty+(py+1)%3;
}
}
else{//都是已知生物。两个生物在同个树则判断真假,两个生物在不同树则合并两个树
if(px/3!=py/3){//两个生物在不同树
if(d==1){
fa[px]=py;fa[tx+(px+1)%3]=ty+(py+1)%3;fa[tx+(px+2)%3]=ty+(py+2)%3;
}
else{//x吃y
fa[px]=ty+(py+2)%3;fa[tx+(px+1)%3]=py;fa[tx+(px+2)%3]=ty+(py+1)%3;
}
}
else{//两个生物在同个树
if(d==1){
if(px!=py) res++;
}
if(d==2){//x吃y
if(px==py || (px-py+3)%3==1)//px与py同类,或px被py吃
res++;
}
}
}
}
cout<<res;
return 0;
}
方法2
对于输入的n,方法1需要建立3n个节点来储存各生物之间的关系,其实这是多余的,可以一种利用更省空间的数据结构来维护这种种类关系——带权并查集。
由于普通的并查集只能知道一个节点的根节点是谁,而没有“与根节点的关系”这个概念,带权并查集就提出了“节点的权值”来表示它与根节点之间的关系。
因此,在带权并查集中,同属于一棵树的节点不一定就是同类的,而只是确定了相互关系的节点,节点的类别由它的权值来决定。
针对该题,我们可以建立一个fa[]数组来表示父节点,一个rank[]数组来表示节点的权值,rank[i]就表示动物i与其根节点动物的关系,这种关系有三中情况:
0,同类 1,吃根节点 2,被根节点吃
rank[]的初始值均为0,维护rank[]的方式如下:
int find(int x){
if(x!=fa[x]){
int t=fa[x];
fa[x]=find(fa[x]);
rank[x]=(rank[x]+rank[t]+3)%3 ;
}
return fa[x];
}
已知两个节点x,y的权值,它们的相互关系就是(rank[x]-rank[y]+3)%3
判断相互关系与输入是否相同,就知道这句话是不是假话
代码:
#include <cstdio>
#include <iostream>
using namespace std;
int fa[50005];
int rank[50005];
int vis[50005];
int find(int x){
if(x!=fa[x]){
int t=fa[x];
fa[x]=find(fa[x]);
rank[x]=(rank[x]+rank[t]+3)%3 ;
}
return fa[x];
}
int main()
{
int n,k,d,x,y;
int i,j;
int relation;
cin>>n>>k;
for(i=1;i<=n;i++){
rank[i]=0;
fa[i]=i;
vis[i]=0;
}
int res=0;
while(k--){
scanf("%d%d%d",&d,&x,&y);
if(d==2 && x==y){res++; continue;}
if(x>n || y>n){res++;continue;}
d--;//0表示同族,1表示x吃y
int px=find(x),py=find(y);
if(vis[x]==0 || vis[y]==0){
vis[x]=vis[y]=1;
fa[px]=py;
rank[px]=(d+rank[y]-rank[x]+3)%3;
}
else{//都是已知物种
if(px==py){//关系已知,判断正误
relation=(rank[x]-rank[y]+3)%3;
if(d!=relation) res++;
}
else{//关系未知,合并两树
fa[px]=py;
rank[px]=(d+rank[y]-rank[x]+3)%3;
}
}
}
cout<<res<<endl;
return 0;
}