带权并查集 学习笔记
例题
[NOI2002] 银河英雄传说
题目传送门
算法实现
我们发现操作只有合并没有分解,所以考虑并查集。
但是好像普通的并查集貌似解决不了,所以我们要使用带权并查集。
我们需要维护两个数组:\(v,w\) , \(v_i\) 代表该节点以下的节点的个数, \(w_i\) 代表该节点以上的节点个数。
显然,节点 \(i,j\) 的距离(保证在同一队)是 \(|v_i-v_j|\)
考虑如何进行路径压缩。
看代码吧
int getfa(int x){
if(x==f[x]) return x;
int tmp=f[x];//以防f[x]改变
f[x]=getfa(f[x]);
w[x]+=w[tmp];//更新w[x]
return f[x];
}
修改很简单,首先要把这条边接过去,然后处理上下的 \(w\) 和 \(v\) 即可。
f[x]=y;
w[x]+=v[y];
v[y]+=v[x];
最后记得初始化
for(int i=1;i<=30000;i++) f[i]=i,v[i]=1,w[i]=0;
代码
已经进行防抄袭处理,删了几个地方
#include<cstdio>
#define abs(x) ((x)>0?(x):-(x))
#define maxn 30039
using namespace std;
//#define debug
typedef int Type;
inline Type read(){
Type sum=0;
int flag=0;
char c=getchar();
while((c<'0'||c>'9')&&c!='-') c=getchar();
if(c=='-') c=getchar(),flag=1;
while('0'<=c&&c<='9'){
sum=(sum<<1)+(sum<<3)+(c^48);
c=getchar();
}
if(flag) return -sum;
return sum;
}
char c;
int T,a,b,x,y;
int f[maxn],v[maxn],w[maxn];
int getfa(int x){
if(x==f[x]) return x;
int tmp=f[x];
f[x]=getfa(f[x]);
w[x]+=w[tmp];
return f[x];
}
int main(){
for(int i=1;i<=30000;i++) f[i]=i,v[i]=1,w[i]=0;
while(T--){
c=getchar(); while(c!='M'&&c!='C') c=getchar();
a=read(); b=read();
x=getfa(a); y=getfa(b);
if(c=='M'){
w[x]+=v[y];
v[y]+=v[x];
}
else{
if(x!=y) printf("-1\n");
else printf("%d\n",abs(w[a]-w[b])-1);
}
}
return 0;
}