带权并查集 POJ 1182 食物链

博客主要讲解带权并查集,指出理解偏移量和路径压缩过程中偏移量的变化是关键。介绍了路径压缩可使树深度不超2,还说明了加入新边时偏移量的计算,以及路径压缩时偏移量的变化,本题偏移量需对3取模。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

带权并查集:

初学并查集的时候不太理解带权并查集,现在感觉主要是理解一下偏移量 和 路径压缩过程中偏移量的变化,就应该很好理解带权并查集了。

首先考虑路径压缩。

int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

这样可以做到每次find后,x的祖先要么是它本身,要么是它的父亲节点,树的深度不超过2。

 

令val[u]表示节点u到父亲节点fu的偏移量,val[v]表示节点v到父亲节点fv的偏移量。

加入新的边 (u,v,w)时,若令fa[fu]=fv,则有val[fu] = val[v]+w-val[u]

上图中u到v的偏移量就为 val[u]+val[v]+w-val[u] - val[v] = w,即u到新的根节点的偏移量减去v到新的根节点的偏移量,显然与新加入的边效果是一样的。

假设此后加入新的边 (u,v1,w1)

在执行find(u)操作时,进行路径压缩,偏移量的变化其实就是把原来路径上的总偏移量都加上,形成新的偏移量

比如下图val[u]+val[v]+w-val[u]=val[v]+w,路径压缩后u到fv的偏移量就是val[v]+w。

路径压缩后连边又和起始一样了。

int find(int x){
	if(fa[x]!=x){
		int tt=fa[x];
		fa[x]=find(fa[x]);
		val[x]=val[x]+val[tt];
	}
	return fa[x];
}

本题由于三种动物形成了一个环,那么偏移量应该始终在0-2之间,即对3取模。

(吐槽:HDU有些题不提示多组数据;POJ则有很多单组数据的题,输入后可能有些奇奇怪怪的东西,用判断EOF的方式读入就会出错) 

代码:

#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int MAXN=5e4+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=3;
const int seed=131;
int fa[MAXN],val[MAXN];
int n,k;
void init(int n){
	For(i,1,n) fa[i]=i,val[i]=0;
}
int find(int x){
	if(fa[x]!=x){
		int tt=fa[x];
		fa[x]=find(fa[x]);
		val[x]=(val[x]+val[tt])%mod;
	}
	return fa[x];
}
int cal(int x,int y){//计算偏移量
	return (x-y+mod)%mod;
}
int main(){
//	freopen("in.txt","r",stdin);
	scanf("%d %d",&n,&k);
	init(n);
	int opt,u,v,w,fu,fv,ans=0;
	For(i,1,k){
		scanf("%d %d %d",&opt,&u,&v);
		if(u>n||v>n||u<1||v<1){
			ans++;
			continue;
		}
		fu=find(u),fv=find(v),w=opt-1;//w为u到v的偏移量,恰好是操作数减1
		if(fu!=fv){
			fa[fu]=fv;
			val[fu]=(val[v]+w-val[u]+mod)%mod;
		}
		else if(fu==fv&&cal(val[u],val[v])!=w) ans++;
	}
	cout<<ans<<"\n";
    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) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值