并查集

并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
顾名思义,并查集支持两种操作:

  • 合并(Union):合并两个元素所属集合(合并对应的树)

  • 查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于判断两个元素是否属于同一集合

并查集在经过修改后可以支持单个元素的删除、移动;使用动态开点线段树还可以实现可持久化并查集。 ——摘自OIwiki

基础

没啥好说,之前已经写过无数次了。
查找:ll find(ll x){return x==f[x]?x:f[x]=find(f[x])}注意有时候不需要路径压缩,比如用启发式合并的时候。
合并:f[find(x)]=find(y)
还有一些操作根据题目而定。

带权并查集

顾名思义,就是并查集的边带了权值来表示该节点和根节点的关系,视具体情况而定。那么我们在合并和路径压缩的时候也要对权值进行相应的维护。
这个的话画个图理解一下就好了,整一点过来:
find的时候更新权值:

ll find(ll x){
	if(x==f[x]) return x;
	ll fa=f[x];
	f[x]=find(f[x]);
	dis[x]+=dis[fa];
	return f[x];
}

merge的时候更新权值:

void merge(ll a,ll b,ll k){ //k是合并时新增的边的权值
	ll x=find(a),y=find(b);
	if(x==y) return; //别忘了
	f[x]=y;
	dis[x]=-dis[a]+k+dis[b]; //一般是这样的,具体视题目而定
}

何姐让去做数学题了,之后再来
好的回来了,不过已经21:37了/fn

POJ1703 Find them, Catch them

如果糖的类型相同,那么 dis[x]=0,反之 dis[x]=1。如果两颗糖不在同一集合中,则无法确定关系。若 (dis[x]-dis[y])%2==0,则类型相同,反之不同。
这题不用scanf/printf要TLE,关同步流也不行/qd

code

点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
#define ll int
const ll N=114514,M=1919810;
ll T,n,m,dis[N],f[N];
inline ll find(ll x){
	if(x==f[x]) return x;
	ll fa=f[x];
	f[x]=find(f[x]);
	dis[x]+=dis[fa];
	return f[x];
}
void merge(ll a,ll b,ll k){
	ll x=find(a),y=find(b);
	if(x==y) return;
	f[x]=y;
	dis[x]=-dis[a]+dis[b]+k;
}
ll mod(ll x,const ll mod){
	return (x%mod+mod)%mod;
}
void solve(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<=n;++i) f[i]=i,dis[i]=0;
	for(int i=1;i<=m;++i){
		char opt[2]; ll a,b;
		scanf("%s%d%d",opt,&a,&b);
		if(opt[0]=='A'){
			ll x=find(a),y=find(b);
			if(x!=y){
				printf("Not sure yet.\n");
				continue;
			}
			if((dis[a]-dis[b])%2==0) printf("In the same gang.\n");
			else printf("In different gangs.\n");
		}
		else merge(a,b,1);
	}
}
int main(){
	scanf("%d",&T);
	while(T--) solve();
	return 0;
} 

HDU3038 How Many Answers Are Wrong

考虑转化,区间和的一种转化方式是将它看成差分的形式,具体而言就是将 i=lrai=value 看成 sumrsuml1=value,这样我们每次添加子区间和时就将 l1r 连一条权值为 k 的边,然后正常操作即可。

code

点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
#define ll int
const ll N=2*114514,M=1919810;
ll T,n,m,dis[N],f[N],ans;
inline ll find(ll x){
	if(x==f[x]) return x;
	ll fa=f[x];
	f[x]=find(f[x]);
	dis[x]+=dis[fa];
	return f[x];
}
void merge(ll a,ll b,ll k){
	ll x=find(a),y=find(b);
	if(x==y) return;
	f[x]=y;
	dis[x]=-dis[a]+dis[b]+k;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	while(cin>>n>>m){
		ans=0; //我是沙宝 
		for(int i=0;i<=n;++i) f[i]=i,dis[i]=0;
		for(int i=1;i<=m;++i){
			ll l,r,k;
			cin>>l>>r>>k;
			--l;
			ll x=find(l),y=find(r);
			if(x==y){
				if(dis[r]-dis[l]!=k)
					++ans;
			}
			else merge(r,l,k);//注意我们这里是r向l连边 
		}
		cout<<ans<<'\n';
	}
	return 0;
} 

P1525 [NOIP2010 提高组] 关押罪犯

和第一道题差不多的思路,我们贪心地先将所有关系按 c 降序排序,优先满足 c 值大的关系,然后依次看有没有冲突,也是连边权为 1 的边。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=114514,M=1919810;
ll T,n,m,dis[N],f[N],ans;
struct xx{
	ll a,b,c;
}e[2*N];
bool cmp(xx x,xx y){
	return x.c>y.c;
}
inline ll find(ll x){
	if(x==f[x]) return x;
	ll fa=f[x];
	f[x]=find(f[x]);
	dis[x]+=dis[fa];
	return f[x];
}
ll merge(ll a,ll b,ll k){
	ll x=find(a),y=find(b);
	if(x==y){
		if((dis[a]-dis[b])%2==0)
			return 0;
	}
	else{
		f[x]=y;
		dis[x]=-dis[a]+dis[b]+k;
	}
	return 1;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;++i) cin>>e[i].a>>e[i].b>>e[i].c;
	for(int i=1;i<=n;++i) f[i]=i;
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;++i){
		if(!merge(e[i].a,e[i].b,1)){
			cout<<e[i].c;
			return 0;
		}
	}
	cout<<0;
	return 0;
} 

P1196 [NOI2002] 银河英雄传说

改了一下合并,我们直接 dis[x]=size[y],size[y]+=size[x] 即可。感性理解一下,我们当前已经赋值的 dis[x] 就是他到根的距离,答案是 abs(dis[a]dis[b])1 就相当于是一个差分得出的答案。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=114514,M=1919810;
ll T,f[N],dis[N],siz[N];
inline ll find(ll x){
	if(x==f[x]) return x;
	ll fa=f[x];
	f[x]=find(f[x]);
	dis[x]+=dis[fa];
	return f[x];
}
void merge(ll a,ll b){
	ll x=find(a),y=find(b);
	if(x==y) return;
	f[x]=y;
	dis[x]=siz[y]; //?
	siz[y]+=siz[x];
}
ll query(ll a,ll b){
	ll x=find(a),y=find(b);
	if(x!=y) return -1;
	else return max(0ll,abs(dis[a]-dis[b])-1ll);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>T;
	for(int i=1;i<=3e4;++i) f[i]=i,siz[i]=1;
	for(int i=1;i<=T;++i){
		char opt; ll a,b;
		cin>>opt>>a>>b;
		if(opt=='M') merge(a,b);
		else cout<<query(a,b)<<'\n';
	}
	return 0;
} 

P2024 [NOI2001] 食物链

带权和扩展域都能写。
这道题里面有三种关系,我们设 0 表示同类,1 表示捕食关系,2 表示被捕食关系。
路径压缩时直接(dis[x]+=dis[fa])%=3就行了,手玩一下珂以发现其正确性。合并的时候也模 3 就行了。
要走了先把代码放这里

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=114514,M=1919810;
ll n,m,f[N],dis[N],ans=0;
ll find(ll x){
	if(x==f[x]) return x;
	ll fa=f[x];
	f[x]=find(f[x]);
	dis[x]+=dis[fa],dis[x]%=3;
	return f[x];
}
void merge(ll a,ll b,ll k){
	ll x=find(a),y=find(b);
	if(x==y) return;
	dis[x]=(-dis[a]+dis[b]+k+3)%3; //记得+mod 
	f[x]=y;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) f[i]=i;
	for(int i=1;i<=m;++i){
		ll opt,a,b;
		cin>>opt>>a>>b;
		if(a>n||b>n||(opt==2&&a==b)){
			++ans;
			continue;
		}
		if(opt==1){
			ll x=find(a),y=find(b);
			if(x!=y) merge(a,b,0);
			else if(dis[a]!=dis[b]) ++ans;
		}
		else{
			ll x=find(a),y=find(b);
			if(x!=y) merge(a,b,1);
			else if((dis[a]-dis[b]+3)%3!=1) ++ans;
		}
	}
	cout<<ans;
	return 0;
} 

扩展域并查集

好像功能和带权是一样的,不写了,摆。

posted @   和蜀玩  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示