学习背景
在学习这块内容前,最好要先了解树的轻重链划分(了解过树剖)
题目特征
树上启发式合并,通常是在题目给定了根节点
(通常
),在离线情况下,解决查询某个子树下符合题目条件的答案,并且子树的信息是没法全部存下来的,如查询
及其子树中不同颜色个数
下面还有很多例题,没明白意思可以先看下都是什么题
树上启发式合并思想
暴力就不用说了,直接搜那个点的子树,单次查询复杂度有
,
次查询就到了
而树上启发式合并能够做到
一遍整棵树即可得到所有答案(离线的询问),复杂度在
树上启发式合并非常暴力,在一次
的过程中,统计两遍轻儿子,统计一遍重儿子就能实现
对于父亲节点
来说,他的信息通过合并子节点
的信息来得到,那么这么说我们记录每个点所有的信息不就完了吗?可是这类题目显然是不会给你这个机会的,一般需要记录的信息非常多,每次都记录一下根本实现不了,所以这里我们需要有取舍的保留子节点的信息!,即我们每次保存当前点最后一次搜索的儿子信息,然后再重新搜索其他儿子的信息并且合并上去
对于父亲节点来说,显然最后一次搜索子节点的信息是可以保留的,因为回来我们就要搜父亲的信息了,而这个点的信息不就是父亲的信息吗,所以最后一次搜索子节点非常关键!
而这里我们最后一次搜的子节点就是重儿子,为什么选重儿子呢?因为重儿子是所有子节点里面点的数量最多的,所以包含的信息也最多,我们如果要重新搜索他的话花费的时间要比轻儿子要更多
所以这里的实现过程就是
对于一个点
,先统计完他所有轻儿子的答案,并且在最后回溯到
的时候要删去保存的信息(否则会影响我们搜索重儿子的答案统计)
然后我们再搜重儿子,查询答案并且保存信息(为什么保留信息上面解释了)
最后我们再重新搜一遍轻儿子,保存信息即可(不需要更新答案)
复杂度分析
①一个点的
只会经过轻边
②树剖中轻重链划分的性质——从根结点到任意结点的路所经过的轻重链的个数必定都小于
换一个角度来想,就是一个点最多有
个祖先节点会需要他的信息,那么
个点就是
模板
void dfs(int u, int fa) {//轻重链划分
siz[u] = 1;
for (auto &v: g[u])
if (v != fa) {
dfs(v, u);
siz[u] += siz[v];
if (!son[u] || siz[v] > siz[son[u]])
son[u] = v;
}
}
void upd(int u, int fa, int k) {//将信息加入/撤回
//k = 1的时候是加上信息,k = -1的时候是撤回信息
for (auto &v: g[u])
if (v != fa && !vis[v]) upd(v, u, k);
}
void dsu(int u, int fa, int keep) {//u为当前点, fa为父节点, keep为是否保留信息
for (auto &v: g[u])//先搜我们的轻儿子,过程中不保留信息
if (v != fa && v != son[u]) dsu(v, u, 0);
if (son[u]) dsu(son[u], u, 1), vis[son[u]] = 1;//然后搜我们的重儿子, 过程中保留信息
upd(u, fa, 1);//然后再查一遍我们的轻儿子,把信息更新上去
//这里用来统计答案
if (son[u]) vis[son[u]] = 0;
if (!keep) upd(u, fa, -1);//如果信息不保留,那我们就撤回
}
练习题
1. CF600E - Lomsat gelral
题意
个节点以点
为根的树,每一个节点有一个颜色
,对于一个以
为根的子树来说,他的子树中的节点有多种颜色,现在求出出现次数最多的颜色(可能有多个颜色出现次数相同),并将出现次数求和
输出以所有点的答案
分析
以点1为根,我们需要保存的信息为当前点子树中颜色出现次数,并且在统计答案的时候找到颜色最多的
这里我们可以用一个桶
来记录颜色
出现的次数,然后用
来记录当前次数最多的颜色,
记录次数最多的颜色的次数之和
这样当某种颜色出现次数
的时候,我们将
更新,并且
(为什么不是加上答案呢,因为我们最大值已经更新了,所以实际上是
)
当
的时候,
不必更新,
当
的时候,啥都不用做
代码
部分代码下面, 全部代码这里
int cnt[MAX], vis[MAX], mx;//mx记录当前
ll ans[MAX], sum;
void upd(int u, int fa, int k) {
cnt[color[u]] += k;
if (k == 1 && cnt[color[u]] >= mx) {
if (cnt[color[u]] > mx) mx = cnt[color[u]], sum = 0;
sum += color[u];
}
for (auto &v: g[u])
if (v != fa && !vis[v]) upd(v, u, k);
}
void dsu(int u, int fa, int keep) {
for (auto &v: g[u])
if (v != fa && v != son[u]) dsu(v, u, 0);
if (son[u]) dsu(son[u], u, 1), vis[son[u]] = 1;
upd(u, fa, 1);
ans[u] = sum;
if (son[u]) vis[son[u]] = 0;
if (!keep) upd(u, fa, -1), mx = sum = 0;
}