启发式合并
启发式合并
启发式合并就是每次把小的集合丢到大的集合中
这样子小的 sz 至少是变成两倍的, 所以每个元素最多被遍历 log 次, 因此复杂度是 O(nlogn)的

#include <bits/stdc++.h> using namespace std; #define endl "\n" typedef long long ll; const int N = 2e5 + 100; set<int> s[N]; void solve() { int n, q; cin >> n >> q; for (int i = 1; i <= n; i++) { int x; cin >> x; s[i].insert(x); } while (q--) { int l, r; cin >> l >> r; if (s[l].size() > s[r].size()) swap(s[l], s[r]); while (s[l].size()) { s[r].insert(*s[l].begin()); s[l].erase(s[l].begin()); } cout << (int)s[r].size() << endl; } } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); solve(); return 0; }
树上启发式合并
树上启发式合并通过树链剖分优化时间复杂度
重链剖分复杂度是 O(nlogn)
长链剖分复杂度是 O(n)

#include <bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; // 树上启发式合并是离线的 const int N = 1e5 + 100; int col[N], cnt[N], res[N], ans, maxn; vector<int> g[N]; void update(int x, int v) { cnt[col[x]] += v; if (cnt[col[x]] > maxn) maxn = cnt[col[x]], ans = col[x]; else if (cnt[col[x]] == maxn) ans += col[x]; } // 链剖不求top数组(链顶上面的点)版本启发式合并 int sz[N], son[N], L[N], R[N], id[N], dfn; void Dfs(int x, int fa) { sz[x] = 1; L[x] = ++dfn; id[dfn] = x; for (auto y : g[x]) { if (y == fa) continue; Dfs(y, x); sz[x] += sz[y]; if (sz[y] > sz[son[x]]) son[x] = y; } R[x] = dfn; } // del 表示是否要 init 当前信息 // 启发式合并中我们在每条链顶部节点 init void DFS(int x, int fa, int del) { for (auto y : g[x]) { if (y == son[x] || y == fa) continue; // 每个点的轻儿子都是链最顶部点 DFS(y, x, 1); } // 先走轻儿子, 省掉重儿子清空时间 if (son[x]) DFS(son[x], x, 0); for (auto y : g[x]) { if (y == son[x] || y == fa) continue; // 对轻儿子子树暴力计算 for (int i = L[y]; i <= R[y]; i++) update(id[i], 1); } // 加上该点贡献 update(x, 1); res[x] = ans; // init if (del) { for (int i = L[x]; i <= R[x]; i++) update(id[i], -1); maxn = ans = 0; } } void solve() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> col[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } Dfs(1, 0); DFS(1, 0, 1); for (int i = 1; i <= n; i++) cout << res[i] << ' '; cout << endl; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); // int T = 1; cin >> T; // while (T--) solve(); solve(); return 0; }

#include <bits/stdc++.h> using namespace std; #define endl "\n" typedef long long ll; const int N = 4e5 + 100; int v[N], root[N], id[N], ans[N], maxdep; vector<int> g[N]; map<string, int> mp; vector<array<int, 2>> que[N]; set<int> s[N]; // 链剖不求top数组(链顶上面的点)版本启发式合并 int sz[N], son[N], L[N], R[N], dep[N], rev[N], dfn; void Dfs(int x, int fa) { sz[x] = 1; L[x] = ++dfn; dep[x] = dep[fa] + 1; rev[dfn] = x; maxdep = max(maxdep, dep[x]); for (auto y : g[x]) { if (y == fa) continue; Dfs(y, x); sz[x] += sz[y]; if (sz[y] > sz[son[x]]) son[x] = y; } R[x] = dfn; } void DFS(int x, int fa, int del) { for (auto y : g[x]) { if(y == fa || y == son[x]) continue; DFS(y, x, 1); } if (son[x]) DFS(son[x], x, 0); for (auto y : g[x]) { if (y == fa || y == son[x]) continue; for (int i = L[y]; i <= R[y]; i++) s[dep[rev[i]]].insert(id[rev[i]]); } s[dep[x]].insert(id[x]); for (auto [k, id] : que[x]) ans[id] = s[dep[x] + k].size(); if (del) { for (int i = 1; i <= maxdep; i++) s[i].clear(); } } void solve() { int n; cin >> n; int nowid = 0, cntroot = 0; for (int i = 1; i <= n; i++) { string s; cin >> s; int fa; cin >> fa; if (!mp[s]) mp[s] = ++nowid; id[i] = mp[s]; if (!fa) { ++cntroot; root[cntroot] = i; } else { g[i].push_back(fa); g[fa].push_back(i); } } int q; cin >> q; for (int i = 1; i <= q; i++) { int v, k; cin >> v >> k; que[v].push_back({k, i}); } for (int i = 1; i <= cntroot; i++) Dfs(root[i], 0); for (int i = 1; i <= cntroot; i++) DFS(root[i], 0, 1); for (int i = 1; i <= q; i++) cout << ans[i] << endl; } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); // int T = 1; cin >> T; // while (T--) solve(); solve(); return 0; }
这两题处理贡献方式基本上一模一样
- 启发式合并做法

#include <bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; const int N = 2e5 + 100; int col[N], ans; vector<int> g[N]; map<int, int> cnt[N]; void dfs(int x, int fa) { cnt[x][col[x]]++; for (auto y : g[x]) { if (y == fa) continue; dfs(y, x); if (cnt[y].count(col[x])) { ans += cnt[y][col[x]]; cnt[y].erase(col[x]); } if (cnt[y].size() > cnt[x].size()) cnt[x].swap(cnt[y]); for (auto &[i, j] : cnt[y]) { if (cnt[x].count(i)) ans += cnt[x][i] * j; cnt[x][i] += j; } } } void solve() { int n; cin >> n; for (int i = 0; i <= n; i++) g[i].clear(), cnt[i].clear(); for (int i = 1; i <= n; i++) cin >> col[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } ans = 0; dfs(1, 1); cout << ans << endl; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int T = 1; cin >> T; while (T--) solve(); // solve(); return 0; }
- 树上启发式合并做法

#include <bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; const int N = 4e5 + 100; vector<int> g[N]; int col[N], vis[N], ans, now; map<int, int> cnt[N]; // 链剖不求top数组(链顶上面的点)版本启发式合并 int sz[N], son[N]; void Dfs(int x, int fa) { sz[x] = 1; son[x] = 0; for (auto y : g[x]) { if (y == fa) continue; Dfs(y, x); sz[x] += sz[y]; if (sz[y] > sz[son[x]]) son[x] = y; } } void calc1(int x, int fa) { if (vis[col[x]] == 0) { if (col[x] != col[now]) ans += cnt[now][col[x]]; } vis[col[x]]++; for (auto y : g[x]) { if (y == fa) continue; calc1(y, x); } vis[col[x]]--; } void calc2(int x, int fa) { if (vis[col[x]] == 0) { cnt[now][col[x]]++; } vis[col[x]]++; for (auto y : g[x]) { if (y == fa) continue; calc2(y, x); } vis[col[x]]--; } // del 表示是否要 init 当前信息 // 启发式合并中我们在每条链顶部节点 init void DFS(int x, int fa, int del) { for (auto y : g[x]) { if (y == son[x] || y == fa) continue; // 每个点的轻儿子都是链最顶部点 DFS(y, x, 1); } // 先走轻儿子, 重儿子保留信息, 省掉重儿子清空时间 if (son[x]) DFS(son[x], x, 0); now = x; for (auto y : g[x]) { if (y == son[x] || y == fa) continue; // 对轻儿子子树计算贡献 calc1(y, x); calc2(y, x); } // 加上该点贡献 ans += cnt[x][col[x]]; cnt[x][col[x]] = 1; // 清空贡献 if (!del) swap(cnt[fa], cnt[x]); cnt[x].clear(); } void solve() { int n; cin >> n; ans = 0; for (int i = 0; i <= n; i++) g[i].clear(), cnt[i].clear(), vis[i] = sz[i] = son[i] = 0; for (int i = 1; i <= n; i++) cin >> col[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } Dfs(1, 1); DFS(1, 1, 1); cout << ans << endl; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int T = 1; cin >> T; while (T--) solve(); // solve(); return 0; }

#include <bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; // 处理每个 val 可以提供的因子数( 1e5 内的数最多的也就 100 个) // 把每个子树能提供的因子拿桶记录下来, 如果桶重对应值大于 2 就是可能作为答案的值 const int N = 1e5 + 100; int val[N]; vector<int> g[N], fac[N]; array<int, 2> ans[N]; int sz[N], son[N], L[N], R[N], pos[N], dfn; void Dfs(int x, int fa) { L[x] = ++dfn; sz[x] = 1; pos[dfn] = x; for (auto y : g[x]) { if (y == fa) continue; Dfs(y, x); sz[x] += sz[y]; if (sz[y] > sz[son[x]]) son[x] = y; } R[x] = dfn; } int vis[N], maxn, cnt; void calc1(int x) { for (int i = L[x]; i <= R[x]; i++) { for (auto j : fac[pos[i]]) { if (vis[j] && j > maxn) maxn = j, cnt = vis[j]; else if (j == maxn) cnt += vis[j]; } } } void calc2(int x) { for (int i = L[x]; i <= R[x]; i++) { for (auto j : fac[pos[i]]) { vis[j]++; } } } void DFS(int x, int fa, int del) { for (auto y : g[x]) { if (y == fa || y == son[x]) continue; DFS(y, x, 1); } if (son[x]) DFS(son[x], x, 0); maxn = cnt = 0; for (auto y : g[x]) { if (y == fa || y == son[x]) continue; calc1(y); calc2(y); } for (auto j : fac[x]) { if (!vis[j]) continue; if (vis[j] && j > maxn) maxn = j, cnt = vis[j]; else if (j == maxn) cnt += vis[j]; } for (auto j : fac[x]) vis[j]++; ans[x] = {maxn, cnt}; if (del) { for (int i = L[x]; i <= R[x]; i++) { for (auto j : fac[pos[i]]) vis[j]--; } } } void solve() { int n; cin >> n; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } for (int i = 1; i <= n; i++) cin >> val[i]; for (int i = 1; i <= n; i++) { for (int j = 1; j * j <= val[i]; j++) { if (val[i] % j == 0) { fac[i].push_back(j); if (val[i] / j != j) fac[i].push_back(val[i] / j); } } } Dfs(1, 1); DFS(1, 1, 1); for (int i = 1; i <= n; i++) { auto [x, y] = ans[i]; cout << x << ' ' << y << endl; } } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); // int T = 1; cin >> T; // while (T--) solve(); solve(); return 0; }
长链剖分, 线段树维护

#include <bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; const int N = 2e5 + 100; int val[N], sz; vector<int> g[N]; vector<array<int, 3>> que[N]; struct node { int maxn, minn, sum; } Seg[N * 4]; node pushup(node l, node r) { node ans; ans.sum = l.sum + r.sum; ans.maxn = max(l.maxn, r.maxn); ans.minn = min(l.minn, r.minn); return ans; } void pushup(int id) { Seg[id] = pushup(Seg[id * 2], Seg[id * 2 + 1]); } void build(int id, int l, int r) { if (l == r) { Seg[id] = {LLONG_MIN / 2, LLONG_MAX / 2, 0}; return; } int mid = l + r >> 1; build(id * 2, l, mid); build(id * 2 + 1, mid + 1, r); pushup(id); } void modify(int id, int l, int r, int pos, node v) { if (l == r) { Seg[id].maxn = max(Seg[id].maxn, v.maxn); Seg[id].minn = min(Seg[id].minn, v.minn); Seg[id].sum += v.sum; return; } int mid = l + r >> 1; if (pos <= mid) modify(id * 2, l, mid, pos, v); else modify(id * 2 + 1, mid + 1, r, pos, v); pushup(id); } node query(int id, int l, int r, int x, int y) { if (x <= l && y >= r) return Seg[id]; int mid = l + r >> 1; node ans = {LLONG_MIN / 2, LLONG_MAX / 2, 0}; if (x <= mid) ans = pushup(ans, query(id * 2, l, mid, x, y)); if (y > mid) ans = pushup(ans, query(id * 2 + 1, mid + 1, r, x, y)); return ans; } int son[N], len[N], L[N], R[N], pos[N], dfn; void Dfs(int x, int fa) { for (auto y : g[x]) { if (y == fa) continue; Dfs(y, x); if (len[son[x]] < len[y]) son[x] = y; } len[x] = len[son[x]] + 1; } void DFS(int x, int fa) { L[x] = ++dfn; pos[dfn] = x; R[x] = L[x] + len[x] - 1; if (son[x]) DFS(son[x], x); for (auto y : g[x]) { if (y == fa || y == son[x]) continue; DFS(y, x); } } node ans[N]; void DSU(int x, int fa) { if (son[x]) DSU(son[x], x); for (auto y : g[x]) { if (y == fa || y == son[x]) continue; DSU(y, x); // 轻(短)链信息全合并到(长)重链上了??? for (int i = L[y], dep = 1; i <= R[y]; i++, dep++) modify(1, 1, sz, L[x] + dep, query(1, 1, sz, i, i)); } modify(1, 1, sz, L[x], {val[x], val[x], val[x]}); for (auto [l, r, i] : que[x]) ans[i] = query(1, 1, sz, L[x] + l, L[x] + r); } void solve() { int n; cin >> n; sz = N / 2; for (int i = 1; i <= n; i++) cin >> val[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } int q; cin >> q; for (int i = 1; i <= q; i++) { int x, l, r; cin >> x >> l >> r; que[x].push_back({l, r, i}); } build(1, 1, sz); Dfs(1, 1); DFS(1, 1); DSU(1, 1); for (int i = 1; i <= q; i++) { auto [a, b, c] = ans[i]; cout << b << ' ' << a << ' ' << c << endl; } } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); // int T = 1; cin >> T; // while (T--) solve(); solve(); return 0; }
重链剖分,树状数组维护

#include <bits/stdc++.h> using namespace std; #define endl "\n" typedef long long ll; const int N = 1e6 + 100; template <typename T> struct BIT { T tr[N]; int n; void init(int n_) { n = n_; for(int i = 0; i <= n; i++) tr[i] = 0; } int lowbit(int x) {return x & -x;} void add(int x, T v){ for(int i = x; i <= n; i += lowbit(i)) tr[i] += v; } T sum(int x) { T res = 0; for(int i = x; i > 0; i -= lowbit(i)) res += tr[i]; return res; } T sum(int l, int r) {return sum(r) - sum(l - 1);} void add(int l, int r, T v) {add(l, v); add(r + 1, -v);} }; int a[N], ans[N], m; vector<int> g[N]; vector<array<int, 2>> que[N]; BIT<int> T; int sz[N], son[N], L[N], R[N], pos[N], dfn; void Dfs(int x, int fa) { sz[x] = 1; L[x] = ++ dfn; pos[dfn] = x; for (auto y : g[x]) { if (y == fa) continue; Dfs(y, x); sz[x] += sz[y]; if (sz[y] > sz[son[x]]) son[x] = y; } R[x] = dfn; } void DFS(int x, int fa, int del) { for (auto y : g[x]) { if (y == fa || y == son[x]) continue; DFS(y, x, 1); } if (son[x]) DFS(son[x], x, 0); for (auto y : g[x]) { if (y == fa || y == son[x]) continue; for (int i = L[y]; i <= R[y]; i++) T.add(a[pos[i]], m, 1); } T.add(a[x], m, 1); for (auto [k, id] : que[x]) ans[id] = T.sum(k); if (del) { for (int i = L[x]; i <= R[x]; i++) T.add(a[pos[i]], m, -1); } } void solve() { int n; cin >> n; m = N - 100; T.init(m); for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } int q; cin >> q; for (int i = 1; i <= q; i++) { int x, k; cin >> x >> k; que[x].push_back({k, i}); } Dfs(1, 1); DFS(1, 1, 1); for (int i = 1; i <= q; i++) cout << ans[i] << endl; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); // int T = 1; cin >> T; // while (T--) solve(); solve(); return 0; }
重链剖分,比较模板的一题

#include<bits/stdc++.h> using namespace std; #define endl "\n" typedef long long ll; const int N = 1e5 + 100; int ans, col[N], res[N], vis[N]; vector<int> g[N]; void update(int x, int v) { if (vis[x] == 0) { if (v > 0) ans++; else ans--; } vis[x] += v; } int L[N], R[N], sz[N], son[N], pos[N], dfn; void Dfs(int x, int fa) { sz[x] = 1; L[x] = ++dfn; pos[dfn] = x; for (auto y : g[x]) { if (y == fa) continue; Dfs(y, x); sz[x] += sz[y]; if (sz[y] > sz[son[x]]) son[x] = y; } R[x] = dfn; } void DFS(int x, int fa, int del) { for (auto y : g[x]) { if (y == fa || y == son[x]) continue; DFS(y, x, 1); } if (son[x]) DFS(son[x], x, 0); for (auto y : g[x]) { if (y == fa || y == son[x]) continue; for (int i = L[y]; i <= R[y]; i++) update(col[pos[i]], 1); } update(col[x], 1); res[x] = ans; if (del) { for (int i = L[x]; i <= R[x]; i++) update(col[pos[i]], -1); ans = 0; } } void solve() { int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) cin >> col[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } Dfs(1, 1); DFS(1, 1, 1); while (m--) { int x; cin >> x; cout << res[x] << endl; } } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); solve(); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效