带权并查集
一般来说带权并查集由这样几个东西(当然更多情况因题而异)
-
集合大小
-
每个点到集合super_father的距离
-
源点父亲
代码实现就是在基础的并查集上加点料。
int getfa(int x){
//类似回溯,从后往前更新
if(x!=fa[x]){
int i=fa[x];
fa[x]=getfa(fa[x]);
val[x]+=val[i];
}
return fa[x];
}
合并操作类似,但是更为灵活,可以用向量法则简化计算。
(合并操作实际只用添加一个fa,其他的点在需要用时会在getfa里完成更新)
例题1 [NOI2002]银河英雄传说
十分的板子,就是记录上面说的那些东西,query的时候统计s就行了
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,fa[500005],s[500005],siz[500005];
int getfa(int x){
if(fa[x]==x) return x;
int k=fa[x];
fa[x]=getfa(fa[x]);
s[x]+=s[k];
siz[x]=siz[fa[x]];
return fa[x];
}
void merge(int l,int r){
int fal=getfa(l);
int far=getfa(r);
fa[fal]=far;
s[fal]+=siz[far];
siz[fal]+=siz[far];
siz[far]=siz[fal];
}
int qu(int l,int r){
int fal=getfa(l);
int far=getfa(r);
if(fal!=far) return -1;
else return abs(s[l]-s[r])-1;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) fa[i]=i,s[i]=0,siz[i]=1;
for(int i=1;i<=n;i++){
int l,r;
char ss;cin>>ss>>l>>r;
if(ss=='M'){
merge(l,r);
}
else {
cout<<qu(l,r)<<"\n";
}
}
return 0;
}
例题2 How Many Answers Are Wrong
不难发现,若把图画出来,只有新的两点在原图中已经联通的时候才能确定其值,否则合并即可。
(一点trick,因为题目给出的左闭右闭,我们可以l--变为左闭右开,这样方便直接连图)
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int getint(){
int summ=0,f=1;char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')f=-1,ch=getchar();
for(;isdigit(ch);ch=getchar()) summ=(summ<<3)+(summ<<1)+ch-48;
return summ*f;
}
const int M=1e6;
int fa[M],val[M],n,m,ans=0;
int getfa(int x){
if(x!=fa[x]){
int xx=fa[x];
fa[x]=getfa(fa[x]);
val[x]+=val[xx];
}
return fa[x];
}
signed main(){
while(cin>>n>>m){
for(int i=0;i<=n;i++) fa[i]=i,val[i]=0;ans=0;
while(m--){
int l,r,value;
l=getint();r=getint();value=getint();
l--;
int fal=getfa(l),far=getfa(r);
if(fal==far){
if(val[l]-val[r]!=value) ans++;
}
else{
val[fal]=-val[l]+value+val[r];
fa[fal]=far;
}
}
cout<<ans<<endl;
}
return 0;
}
例题3 [NOI2001]食物链
可以用三个并查集暴力。
也可以用带权并查集更加优雅的解决。
我们把 相同、吃、被吃 看做0、1、2 ,然后发现同一个连通块内两点距离对3取模即使其关系。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int getint(){
int summ=0,f=1;char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')f=-1,ch=getchar();
for(;isdigit(ch);ch=getchar()) summ=(summ<<3)+(summ<<1)+ch-48;
return summ*f;
}
const int M=1e6;
int fa[M],val[M],n,m,ans=0;
int getfa(int x){
if(x!=fa[x]){
int i=fa[x];
fa[x]=getfa(fa[x]);
val[x]+=val[i];val[x]%=3;
}
return fa[x];
}
signed main(){
cin>>n>>m;
for(int i=0;i<=n;i++) fa[i]=i,val[i]=0;ans=0;
while(m--){
int l,r,op;
op=getint();l=getint();r=getint();
if(l>n||r>n||(l==r&&op==2)){
ans++;continue;
}
int fal=getfa(l),far=getfa(r);
if(fal==far){
if(op==1) if((((val[l]-val[r])%3+3)%3)!=0) ans++;
if(op==2) if((((val[l]-val[r])%3+3)%3)!=1) ans++;
}
else{
if(op==1) val[fal]=-val[l]+val[r];val[fal]%=3;
if(op==2) val[fal]=-val[l]+val[r]+1;val[fal]%=3;
fa[fal]=far;
}
}
cout<<ans<<endl;
return 0;
}