并查集的启发式合并
背景:遇到一个题要用到,所以总结一下
概述
启发式合并的本质其实就是把size小的集合合并到size大的集合里面
一般在需要查询并查集内元素或合并集合的时候可能会用到启发式合并
比如合并 \([4,5,2,1]\) 和 \([1,2,4,6,8,9]\) 两个集合,我们选择遍历第一个集合合并到第二个集合里
代码以及分析
void merge(vector<int>&a, vector<int>&b) {
if (a.size() > b.size()) for (int x : b) a.push_back(x);
else for (int x : a) b.push_back(x);
}
在这里一个vector就表示一个集合
由于是从小集合合并到大集合,每次合并至少扩大一倍size,所以每个元素最多会被操作 \(O(logN)\) 次
例题
既然清楚了启发式合并的核心思想,那么我们来练一道题加深对它的理解
启发式合并例题
这道题说白了的话就是根据题目中所给的询问建边合并集合,边的权值就是建立完成的时间,问每个元素第一次合并到1所在的集合需要花费多少时间
思路
我们只需要在每次询问进行启发式合并并更新合并的信息即可
最后合并到只剩1所在的集合,时间复杂度 \(O(NlogN)\)
代码
// ξ†(ᗜ ˰ ᗜ)†ξ
// 去吧,鸭鸭,把希儿和AC都带回来!
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int N = 2e5 + 5;
int res[N];
int fa[N];
int sz[N];
vector<int> vec[N];
int find(int x){
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int a,int b,int t){
a = find(a);
b = find(b);
if(a == b) return;
if(a == find(1)){
for(auto x : vec[b]){
res[x] = t;
}
}
else if(b == find(1)){
for(auto x : vec[a]){
res[x] = t;
}
}
//更新操作
if(sz[a] < sz[b]) swap(a,b);
sz[a] += sz[b];
fa[b] = a;
for(auto x : vec[b]){
vec[a].push_back(x);
}
// 启发式合并
}
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline void print(int x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9)
print(x/10);
putchar(x%10+'0');
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for(int i = 1; i <= n; i++){
fa[i] = i;
sz[i] = 1;
vec[i].push_back(i);
res[i] = -1;
}
res[1] = 0;
int m1,m2;
cin >> m1 >> m2;
while(m1--){
int a,b;
cin >> a >> b;
merge(a,b,0);
}
for(int i = 1; i <= m2; i++){
int a,b;
cin >> a >> b;
merge(a,b,i);
}
for(int i = 1; i <= n; i++){
cout << res[i] << endl;
}
return 0;
}