食物链(带权并查集)POJ1182

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值