啟發式合併

end_switch發表於2024-10-08

入門

例題

[ABC329F] Colored Ball

  • 題意

給定 \(N\) 個盒子,每個盒子裡面有一個顏色為 \(C_i\) 的小球。有 \(Q\) 次操作,每次操作將第 \(a_i\) 個盒子中的球都放到第 \(b_i\) 個盒子裡面,你需要在每次操作後輸出當前操作結束後第 \(b_i\) 個盒子裡面有多少個不同顏色的小球。

如果盒子為空,輸出 \(0\) 即可。

首先看到對答案有貢獻的只有小球的顏色,即種類。因此可以聯想到 STL set 實現的自動去重功能。

考慮按題意模擬。若構造一組形如由極小集合合併至極大集合的資料,此演算法的最劣複雜度是 \(O(nq\log n)\) 的。

此時考慮啟發式合併。

我們考慮讓集合中元素個數數量小的合併至大的中。此時可以證明時間複雜度是 \(O(n\log ^2 n)\) 的。

初看上可能感覺這就是個暴力。但是我們分析一下每個元素被 insert 了多少次。

一個集合中的元素被放入另一個集合中會被 insert 一次。但是這個元素所在的集合的大小至少擴大了一倍。所以一個元素最多被 insert \(O(\log n)\) 次。加上 set 本身帶有的 \(O(\log n)\) 的複雜度,最終複雜度是 \(O(n\log ^2 n)\) 的。

在這裡,對於兩個大小不一樣的集合,我們將小的集合合併到大的集合中,而不是將大的集合合併到小的集合中。

為什麼呢?這個集合的大小可以認為是集合的高度(在正常情況下),而我們將集合高度小的併到高度大的顯然有助於我們找到父親。

讓高度小的樹成為高度較大的樹的子樹,這個最佳化可以做到單次 \(O(\log n)\)

while(q--) {
		cin >> x >> y;
		if(s[x].size() < s[y].size()) {
			for(auto i : s[x])
				s[y].insert(i);
			s[x].clear();
			cout << s[y].size() << '\n';
		}
		else {
			for(auto i : s[y])
				s[x].insert(i);
			s[y].clear(), swap(s[x], s[y]);
			cout << s[y].size() << '\n';
		}
	}

P3201 [HNOI2009] 夢幻布丁

dsu on tree

例題

[ABC350G] Mediator

首先一個點是否與詢問點對相鄰,可以轉換為對父親的討論。

下列情況是有解的:

  • \(fa_u = fa_v\)\(u, v\) 不是根節點,答案為 \(fa_u\)
  • \(fa_{fa_u} = v\),答案為 \(fa_u\)
  • \(fa_{fa_v} = u\),答案為 \(fa_v\)

畫圖分析較為移動。

於是我們只需要維護父親即可,考慮啟發式合併。

每次將連通塊大小較小的合併至較大的連通塊內,對於每次這種操作暴力 dfs 修改 \(fa\) 即可。

連通塊大小可以用並查集輕鬆維護。

證明覆雜度:每次連通塊大小最多擴大一倍,所以複雜度 \(O(n\log n)\)

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 1e5 + 5;
const int mod = 998244353;
int n, Q, u, v, op, ans, fa[N];
vector<int> g[N];

inline void dfs(int x, int last) {
	fa[x] = last;
	
	for(auto u : g[x])
		if(u != last) dfs(u, x);
	
	return ;
}

namespace USF {
	int Fa[N], sz[N];
	
	inline void init() {
		for(int i = 1 ; i < N ; ++ i)
			Fa[i] = i, sz[i] = 1;
		
		return ;
	}
	
	inline int find(int x) {
		if(x != Fa[x]) Fa[x] = find(Fa[x]);
		
		return Fa[x];
	}
	
	inline void merge(int x, int y) {
		int fx = find(x), fy = find(y);
		
		if(fx == fy) return ;
		
		if(sz[fx] < sz[fy]) swap(x, y), swap(fx, fy);
		
		dfs(y, x);
		
		sz[fx] += sz[fy], Fa[fy] = fx;
		
		g[x].pb(y), g[y].pb(x);
		
		return ;
	}
}

using namespace USF;

inline int query(int u, int v) {
	if(fa[u] == fa[v] && fa[u]) return fa[u];
	if(fa[fa[u]] == v) return fa[u];
	if(fa[fa[v]] == u) return fa[v];
	
	return 0;
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	
	init();
	
	while(Q --) {
		cin >> op >> u >> v;
		
		op = 1 + ((op * (1 + ans)) % mod) % 2;
		u = 1 + ((u * (1 + ans)) % mod) % n;
		v = 1 + ((v * (1 + ans)) % mod) % n;
		
		if(op == 1) merge(u, v);
		else {
			ans = query(u, v);
			
			cout << ans << '\n';
		}
	}
	
	return 0;
}

相關文章