POJ 1182 食物链 种类并查集 三种

POJ 1182

重要思路如下图所示,细节见代码注释,不懂的见往期博客关于并查集的文章

文字版

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 设A是同类,B是食物,C是天敌 同类吃食物 食物吃天敌 天敌吃同类
  思路:开3N的并查集,1~N是同类,N+1~2N是食物,2N+1~3N是天敌
      即    x本身为i(i∈[1,n])    x的食物为i+n    x的天敌为i+2*n    这是种类并查集的性质1
  重难点1:合并算法 {推导见重难点2
    X和Y是同类:X本身与Y本身是同类    X的食物与Y的食物是同类(易忽略致误)     X的天敌与Y的天敌是同类(易忽略致误)
            即    merge(x,y)            merge(x+n,y+n)                        merge(x+2*n,y+2*n)
    X吃Y:    X本身是Y本身的天敌    X的食物是Y的同类     x的天敌是y的食物(易忽略致误)
        即    merge(x,y+2*n)         merge(x+n,y)        merge(x+2*n,y+n)
    }
  重难点2:同属一个根节点的数学意义:{ 种类并查集的性质2,以此推出merge算法
          i.同属一个根节点等价于属于同一个并查集
          ii.在同一个种类区间内(比如n=5,则1-5是一个种类区间,6-10是另一个种类区间,[1+kn,(k+1)n]是一个种类区间),若两个元素同属一个并查集,则他们是同类
        iii.在不同的种类区间内,若两元素同属一个并查集,则表示一种捕食的关系。
            find(x) ==find(y+2*n),find(x+n)==find(y),find(x+2*n)==find(y+n)    满足任意一种则表示X吃Y
            find(y) ==find(x+2*n),find(y+n)==find(x),find(y+2*n)==find(x+n)    满足任意一种则表示Y吃X
            经1吃2,2吃3验证,3吃1的三种情况均出现了    因此,判断悖论时选最简单的一种即可,比如中间一列情况
    }
    判断第一种错误:
        如果x与y是同类 那么若find(x)==find(y+n)(x吃y)或find(x+n)==find(y)(y吃x) 则错误
        如果x吃y       那么若find(x)==find(y)(x,y同类)或find(x)==find(y+n)(y吃x) 则错误 

#include<cstdio>
#include<iostream>
using namespace std;
const int N=5e4+1,K=1e5+1;
int fa[3*N],rank[3*N]; //种类并查集 三种
int ans=0;//假话数
inline int read() {
	int s=0,w=1;s=0一定要写,除非是全局变量,否则不会初始化!!! 
	char c=getchar();
	while(c<'0' || c>'9') {
		if(c=='-')	w*=-1;
		c=getchar() ;
	}
	while(c>='0' && c<='9')		s=(s<<3)+(s<<1)+c-'0',c=getchar();
	return s*w;
}
int find(int x) { //找根节点 路径压缩 
	return (x==fa[x]) ? x : (fa[x]=find(fa[x]));//按秩合并和路径压缩在可以破坏树的结构情况下可以共存 
}
void init(int n) { //初始化
	for(int i=0;i<=3*n;i++){
		fa[i]=i;rank[i]=0;
	} 
}
void merge(int x,int y) { //按秩合并
	int fx=find(x),fy=find(y);
	if(rank[fx]>=rank[fy]){
		fa[fy]=fx;
	}else	fa[fx]=fy;
	if(rank[fx]==rank[fy] && fx!=fy)	rank[fx]++;
}
int main() {
	int n=read(),k=read();
	init(n);
	for(int i=1; i<=k; i++) {
		int d=read(),x=read(),y=read();
		//先判断是否为悖论,再合并 
		if(x>n || y>n) { //2) 当前的话中X或Y比N大,就是假话;
			ans++;
//			cout<<"悖论2ans="<<ans<<endl;
			continue;
		}
		if(d==2 && x==y) { //3) 当前的话表示X吃X,就是假话。
			ans++;
//			cout<<"悖论3ans="<<ans<<endl;
			continue;
		}
	/*	判断第一种错误:
		如果x与y是同类 那么若find(x)==find(y+n)(x吃y)或find(x+n)==find(y)(y吃x) 则错误
		如果x吃y       那么若find(x)==find(y)(x,y同类)或find(x)==find(y+n)(y吃x) 则错误
		合并算法	X和Y是同类:X本身与Y本身是同类	X的食物与Y的食物是同类(易忽略致误) 	X的天敌与Y的天敌是同类(易忽略致误)
						即		merge(x,y)			merge(x+n,y+n)						merge(x+2*n,y+2*n)
					X吃Y:		X本身是Y本身的天敌	X的食物是Y的同类 	x的天敌是y的食物(易忽略致误)
						即		merge(x,y+2*n)	 	merge(x+n,y)		merge(x+2*n,y+n)								*/
		if(d==1){
			if(find(x)==find(y+n) || find(x+n)==find(y)){//易错点!!括号里是find,要用根节点,不能fa[]用父节点,虽然示例能过吧 
				ans++;
//				cout<<"!ans="<<ans<<endl;
				continue;
			}
			merge(x,y),merge(x+n,y+n),merge(x+2*n,y+2*n);
		}else {
			if(find(x)==find(y) || find(x)==find(y+n)) {
				ans++;
//				cout<<"!!!ans="<<ans<<endl;
				continue;
			}
			merge(x,y+2*n),merge(x+n,y),merge(x+2*n,y+n);
		}
	}
	cout<<ans;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值