POJ1182(食物链)

题目传送门

在这里插入图片描述

题意

这是一道非常经典的并查集题目,或许应该叫种类并查集更好。题目意思很好理解,问题在于怎么去判断每一句话是真话还是假话还是不确定。

思路
  1. x > n || y > n那肯定是假话不用加以其他的判断就能的出来

  2. d == 2 && x == y,既然x要吃掉y,那么x和y肯定不能相等啊,所以这也是一个假话。

  3. 然后就是判断现在说的这句话与前面的话是不是冲突,这也是这道题的核心所在。

  4. 引入概念一:s[x]数组代表x 的当前父节点是 s[x],切记是当前父节点

  5. 引入概念二:r[x]数组表示x 与 s[x]父节点的关系,r[x] = 0代表x 与 s[x]属于同类,r[x] = 1代表 x 可以吃掉 s[x],r[x] = 2代表 x会被 s[x]吃掉,如下图
    在这里插入图片描述
    现在问题就转化成如何在并查集中维护子节点和父节点的关系,并且能够轻而易举的知道两则的关系,大概有三种核心操作:路径压缩关系转化,同类判定,x吃y是否成立,节点合并。

  6. 路径压缩关系转化
    在这里插入图片描述
    路径压缩之前r[x]代表的是x 和 y的关系,压缩之后需要把r[x]转化为 x 和 z的关系,向量xz是要求的,向量xy = r[x],向量yz = r[y],可以推出压缩之后r[x] = (r[x] + r[y])%3,为什么要%3?假如r[x] 和 r[y]都为2,压缩之后应该是 1 而不是 4。

  7. 同类判定
    在这里插入图片描述
    只要r[x] = r[y]那么x 和 y就是同类,因为查询之前需要做路径压缩,压缩完之后x 和 y都是根节点的孩子,如果他们与根节点的关系一致那么他们就是同类,前提是他们在同一个根节点下。

  8. x吃y是否成立
    在这里插入图片描述

向量xy = 向量xz - 向量yz,进而转化为r[x] - r[y] == 1是否成立,有几个小细节,r[x] - r[y]可能出现负数,所以需要 + 3 处理然后整体%3,在判断是不是等于1,如果成立那么 x 吃 y这句话就是对的。

  1. 节点合并(最难的)
    在这里插入图片描述

大概关系就在上面一张图里了,合并x 和 y就是需要合并 a 和 b;x 吃 y是d = 1,输入的d是2,x 和 y是同类 d = 0,输入的d = 1;所以两种操作都要减掉1。然后还是可能会出现负数的情况,所以老规矩 + 3 然后对3取余就是父节点a 和 b的关系。

所有的东西都在上面了,下面贴一份代码参考,如有不对的地方请指正。这道题还有最后一个坑点就是不能多组输入,多组输入就WA,不知道什么原因也不敢问。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 50010;
int s[maxn];
int r[maxn];
void clear_set()
{
	for(int i = 0;i < maxn;i++){
		s[i] = i;
	}
	memset(r,0,sizeof(r));
}
int find_set(int x)
{
	if(x != s[x]){
		int f = s[x];
		s[x] = find_set(s[x]);				//路径压缩
		r[x] = (r[x] + r[f])%3;				//更新子节点与根节点的关系 
		return s[x]; 
	}
	return s[x];
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);					//单组读入就能过,多组读入就WA 
	clear_set(); 
	int ans = 0;
	while(m--){
		int d,x,y;
		scanf("%d%d%d",&d,&x,&y);
		if(x > n || y > n){				//假话2
			ans++;
			continue;
		}
		if(d == 2 && x == y){			//假话3 
			ans++;
			continue;
		}
		int fx = find_set(x);
		int fy = find_set(y); 	
		if(fx != fy){
			s[fx] = fy;
			r[fx] = (r[y] - r[x] + (d-1) + 3)%3;
		}
		else{
			if(d == 1 && r[x] != r[y]){						//假话1冲突		
				ans++;
			}
			if(d == 2 && ((3+r[x] - r[y]) % 3 != 1)){		//假话1冲突 
				ans++;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}	

愿你走出半生,归来仍是少年~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值