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

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

### 并查集算法的时间复杂度分析 并查集是一种高效的用于处理集合合并与查询的算法。在POJ 1182 食物链问题中,使用了并查集来判断动物之间的关系,并且通过路径压缩和按秩合并等优化手段,可以极大地提高算法的效率。 #### 路径压缩的影响 路径压缩是并查集中一种重要的优化技术,它能够将查找过程中经过的所有节点直接连接到根节点上。这种操作使得后续查找的时间复杂度接近于常数[^1]。具体来说,路径压缩后的查找操作时间复杂度可以用阿克曼函数的反函数 \( \alpha(n) \) 来表示,其中 \( n \) 是集合中的元素个数。阿克曼函数的增长速度极慢,因此 \( \alpha(n) \) 在实际应用中几乎可以视为常数。 ```python def Find(x): if x != par[x]: par[x] = Find(par[x]) # 路径压缩 return par[x] ``` #### 按秩合并的作用 按秩合并是一种优化策略,它通过将较小的树合并到较大的树上来减少树的高度。这种方法结合路径压缩后,可以进一步降低操作的时间复杂度[^2]。在实际实现中,可以通过维护一个数组 `rank` 来记录每个集合的深度,并在合并时选择深度较小的树挂接到深度较大的树上。 ```python def Union(x, y): rootX = Find(x) rootY = Find(y) if rootX != rootY: if rank[rootX] > rank[rootY]: par[rootY] = rootX elif rank[rootX] < rank[rootY]: par[rootX] = rootY else: par[rootY] = rootX rank[rootX] += 1 ``` #### 时间复杂度总结 对于 POJ 1182 食物链问题,假设总共有 \( n \) 个动物和 \( m \) 条关系,则初始化并查集的时间复杂度为 \( O(n) \),每次查找或合并操作的时间复杂度为 \( O(\alpha(n)) \)[^2]。由于 \( \alpha(n) \) 的增长极其缓慢,在实际情况下可以认为其为常数。因此,整个算法的时间复杂度主要由关系数量 \( m \) 决定,最终的时间复杂度为 \( O(m \cdot \alpha(n)) \)[^1]。 ### 代码示例 以下是一个完整的并查集实现,适用于 POJ 1182 食物链问题: ```python class UnionFind: def __init__(self, n): self.par = list(range(3 * n)) self.rank = [0] * (3 * n) def Find(self, x): if self.par[x] != x: self.par[x] = self.Find(self.par[x]) return self.par[x] def Union(self, x, y): rootX = self.Find(x) rootY = self.Find(y) if rootX != rootY: if self.rank[rootX] > self.rank[rootY]: self.par[rootY] = rootX elif self.rank[rootX] < self.rank[rootY]: self.par[rootX] = rootY else: self.par[rootY] = rootX self.rank[rootX] += 1 def Same(self, x, y): return self.Find(x) == self.Find(y) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值