Dsu on tree学习笔记
前置知识:重链剖分
Dsu on tree
算法简介
考虑这样的一类树上统计问题,无修改且允许离线,统计子树信息。
这就是dsu on tree 处理的问题。
算法流程
考虑暴力怎么做,遍历每个子节点的贡献->处理答案->删除子节点贡献。
dsu on tree的实现 :
- 遍历所有轻儿子,递归时删除贡献。
- 遍历重儿子,保留贡献。
- 处理轻儿子的贡献。
- 处理当前节点的答案。
- 如果该点为轻儿子,删除该点贡献。
为什么不保留多个节点的贡献?
因为会混淆子树之间的信息,由于我们最后处理重儿子的答案,所以递归之后当前子树需要用到他的重儿子贡献,如果都保存的话,遍历下一个子树时会将当前子树的贡献也算进去,所以只能保留一个子树的答案,因为重儿子的子树大小最大,所以保存下之后递归回去需要处理的子节点数更少,从而达到降低复杂度的目的。
主体框架
void dfs1(int u) { // 重剖
siz[u] = 1;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
dfs1(v); siz[u] += siz[v]; if (siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int fa, int opt) { // opt=1表示保留贡献,否则表示删除
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v; if (v == son[u] || v == fa) continue ;
dfs2(v, u, 0);//处理轻儿子答案
}
if (son[u]) dfs2(son[u], u, 1), Son = son[u];//处理重儿子答案并保存
calc(u);//统计轻儿子贡献
/*
处理当前节点答案
*/
Son = 0;
if (!opt) del(u); // 如果该点轻儿子,删除贡献
//注意:轻儿子的重子节点也要删
}
这里不懂没关系,结合例题再去理解。
例题
CF600E Lomsat gelral
题目描述
给你一棵以结点 \(1\) 为根的有根树,每个节点最开始都被涂上了颜色。
如果颜色 \(c\) 在以结点 \(v\) 为根的子树中出现次数最多,则称其在以结点 \(v\) 为根的子树中占重要地位。一棵树中可以有很多颜色同时占重要地位。
以 \(v\) 为根的子树指结点 \(v\) 及其他到根结点的路径包含 \(v\) 的结点。
请输出对于每一个结点 \(v\),在其子树中占重要地位的颜色编号之和。
Solution
板子,没啥好说的。
细节见代码 code
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, c[N], siz[N], son[N], mx, sum, cnt[N], Son, ans[N];
vector<int> g[N];
void dfs1(int u, int fa) {
siz[u] = 1;
for (auto v : g[u]) {
if (v == fa) continue ;
dfs1(v, u);
siz[u] += siz[v];
if (siz[son[u]] < siz[v]) son[u] = v;
}
}
inline void add(int u, int fa, int val) {
cnt[c[u]] += val;
if (cnt[c[u]] > mx) mx = cnt[c[u]], sum = c[u];
else if (cnt[c[u]] == mx) sum += c[u];
for (auto v : g[u]) {
if (v == fa || v == Son) continue ;
add(v, u, val);
}
}
void dfs2(int u, int fa, int opt) {
for (auto v : g[u]) {
if (v == fa || v == son[u]) continue ;
dfs2(v, u, 0);
}
if (son[u]) dfs2(son[u], u, 1), Son = son[u];
add(u, fa, 1), Son = 0;
ans[u] = sum;
if (!opt) add(u, fa, -1), sum = 0, mx = 0;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> c[i];
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
g[u].push_back(v), g[v].push_back(u);
}
dfs1(1, 0), dfs2(1, 0, 0);
for (int i = 1; i <= n; i++) cout << ans[i] << " ";
return 0;
}
P9233 [蓝桥杯 2023 省 A] 颜色平衡树
题目描述
给定一棵树,结点由 \(1\) 至 \(n\) 编号,其中结点 \(1\) 是树根。树的每个点有一个颜色 \(C_i\)。
如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。
求出这棵树中有多少个子树是颜色平衡树。
Solution
也是模板,用一个桶维护每种颜色出现了多少次,再维护一下出现的颜色数量,如果 \(cnt_c \times tot = siz_u\),答案加 \(1\)。
其中 \(cnt_c\) 为颜色 \(c\) 的出现次数,\(tot\) 为颜色数,\(siz_u\) 为当前节点 \(u\) 的子树大小。
code
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define rd read()
using namespace std;
const int N = 2e5 + 5;
struct Edge {
int v, next;
} e[N];
inline int read() {
int x = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return x;
}
int ans, c[N], n, head[N], cnt, siz[N], son[N], Son, col[N], tot;
inline void add(int u, int v) {e[++cnt] = {v, head[u]}; head[u] = cnt;}
void dfs1(int u) {
siz[u] = 1;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
dfs1(v); siz[u] += siz[v]; if (siz[son[u]] < siz[v]) son[u] = v;
}
}
inline void calc(int u, int val) {
col[c[u]] += val;
if (col[c[u]] == 1 && val == 1) tot++;
if (col[c[u]] == 0 && val == -1) tot--;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v; if (v == Son) continue ;
calc(v, val);
}
}
void dfs2(int u, int opt) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v; if (v == son[u]) continue ;
dfs2(v, 0);
}
if (son[u]) dfs2(son[u], 1), Son = son[u];
calc(u, 1);
if (tot * col[c[u]] == siz[u]) ans++;
Son = 0;
if (!opt) calc(u, -1), tot = 0;
}
signed main() {
n = rd; for (int i = 1; i <= n; i++) c[i] = rd, add(rd, i);
dfs1(1); dfs2(1, 0);
cout << ans;
return 0;
}

浙公网安备 33010602011771号