入門
例題
[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;
}