树上启发式合并 学习笔记
本文目录(7 节)
树上启发式合并 (dsu on tree) 可以在较优秀的复杂度内解决某些树上离线问题。
引入
考虑这样一个问题:
- 给一棵 个点的有根树,求每棵子树的颜色种类数。
考虑暴力,当我们要求 的子树的答案时,我们可以用一个 数组记录每个颜色出现的次数,将 子树遍历一遍后, 数组中不为 的位置的个数就是 子树的颜色种类数,然后清空 数组。
显然这样总复杂度是 的,有没有办法优化一下呢?
dsu on tree
当我们求完 子树的答案后,如果我们立刻要求 子树的答案,那么我们就不必清空 数组,同时遍历 子树时也不需要遍历 子树。
看起来这样似乎并没有优化多少?
那么,如果我们对于每个点 ,在求完其重儿子 的答案后,立刻按照上述方法求 的答案,你可以惊奇的发现时间复杂度被优化到了 !
具体实现见代码:
const int N = 1e5 + 7;
int n, m, a[N], s[N], son[N], dfn[N], num, c[N], now, ans[N];
vi e[N];
void dfs(int x, int f) {
s[x] = 1, dfn[x] = ++num;
for (auto y : e[x]) if (y != f) dfs(y, x), s[x] += s[y], son[x] = s[y] > s[son[x]] ? y : son[x];
}
inline void add(int x) {
now += !c[x]++;
}
void dfs(int x, int f, bool k) {
for (auto y : e[x]) if (y != f && y != son[x]) dfs(y, x, 0);
if (son[x]) dfs(son[x], x, 1);
for (auto y : e[x]) if (y != f && y != son[x]) for (int i = 0; i < s[y]; i++) add(a[dfn[y]+i]);
add(a[dfn[x]]), ans[x] = now;
if (!k) for (int i = now = 0; i < s[x]; i++) c[a[dfn[x]+i]] = 0;
}
int main() {
rd(n);
for (int i = 1, x, y; i < n; i++) rd(x), rd(y), e[x].pb(y), e[y].pb(x);
dfs(1, 0);
for (int i = 1; i <= n; i++) rd(a[dfn[i]]);
dfs(1, 0, 1), rd(m);
for (int i = 1, x; i <= m; i++) rd(x), print(ans[x]);
return 0;
}
证明
首先我们知道一个点到根节点上有 条重链和 条轻边。
注意到每条轻边会导致下面的子树被多遍历一次,因此每个点最多被遍历 次。
因此总时间复杂度为 。
【例题】CF600E Lomsat gelral
求子树众数和,就在上面代码的基础上改一下即可。
const int N = 1e5 + 7;
int n, a[N], b[N], s[N], son[N], dfn[N], num, c[N], mx;
ll now, ans[N];
vi e[N];
void dfs(int x, int f) {
s[x] = 1, dfn[x] = ++num;
for (auto y : e[x]) if (y != f) dfs(y, x), s[x] += s[y], son[x] = s[y] > s[son[x]] ? y : son[x];
}
inline void add(int x) {
if (++c[x] > mx) mx = c[x], now = 0;
if (c[x] == mx) now += x;
}
void dfs(int x, int f, bool k) {
for (auto y : e[x]) if (y != f && y != son[x]) dfs(y, x, 0);
if (son[x]) dfs(son[x], x, 1);
for (auto y : e[x]) if (y != f && y != son[x]) for (int i = 0; i < s[y]; i++) add(a[dfn[y]+i]);
add(a[dfn[x]]), ans[x] = now;
if (!k) for (int i = mx = now = 0; i < s[x]; i++) c[a[dfn[x]+i]] = 0;
}
int main() {
rd(n);
for (int i = 1; i <= n; i++) rd(b[i]);
for (int i = 1, x, y; i < n; i++) rd(x), rd(y), e[x].pb(y), e[y].pb(x);
dfs(1, 0);
for (int i = 1; i <= n; i++) a[dfn[i]] = b[i];
dfs(1, 0, 1);
for (int i = 1; i <= n; i++) print(ans[i], " \n"[i==n]);
return 0;
}
【练习】CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
【练习】CF570D Tree Requests
参考资料
- OI Wiki 树上启发式合并
- p-z-y dsu on tree 学习笔记
评论
暂无已公开评论。