并查集
并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
顾名思义,并查集支持两种操作:
-
合并(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]-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
考虑转化,区间和的一种转化方式是将它看成差分的形式,具体而言就是将 看成 ,这样我们每次添加子区间和时就将 向 连一条权值为 的边,然后正常操作即可。
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 提高组] 关押罪犯
和第一道题差不多的思路,我们贪心地先将所有关系按 降序排序,优先满足 值大的关系,然后依次看有没有冲突,也是连边权为 的边。
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] 银河英雄传说
改了一下合并,我们直接 即可。感性理解一下,我们当前已经赋值的 就是他到根的距离,答案是 就相当于是一个差分得出的答案。
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] 食物链
带权和扩展域都能写。
这道题里面有三种关系,我们设 表示同类, 表示捕食关系, 表示被捕食关系。
路径压缩时直接(dis[x]+=dis[fa])%=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;
}
扩展域并查集
好像功能和带权是一样的,不写了,摆。