目录
1. 前言
点分治,是一种图论算法,专门用于一类树上路径统计问题。
前置知识:无。
2. 详解
2.1 树的重心
讲点分治之前我们先来讲讲树的重心。
树的重心的定义是这样的:在一棵树中,如果以一个点为根,其所有儿子的子树大小最大值是最小的,那么这个点就是树的重心。
换言之,树的重心需要满足其子树的最大值最小。
比如下面这棵树,1 号点就是树的重心。
树的重心有一个很重要的性质:其最大子树大小小于等于 /(/dfrac{n}{2}/)。
那么怎么求树的重心呢?一遍 DFS 就可以啦~
Code:
void Get_Root(int now, int father)
{
Size[now] = 1, Maxn[now] = 0;
for (int i = Head[now]; i; i = Edge[i].Next)
{
int u = Edge[i].to;
if (u == father) continue ;
Get_Root(u, now);
Size[now] += Size[u];
Maxn[now] = Max(Maxn[now], Size[u]);
}
Maxn[now] = Max(Maxn[now], sum - Maxn[now]);
if (Maxn[now] < Maxn[Root]) Root = now;
}
2.2 点分治
淀粉质可真好吃
树的路径分两类:经过根的,不经过根的。
对于这些经过根的,我们可以通过一个 Solve
函数来计算这些路径的贡献。
对于这些不经过根的,我们可以分治一波,在其子树内做同样的事情,这样这些路径最后一定会经过某个根。
比如这样:
在统计完经过根的路径以后,删除这个根,对 3 棵圈起来的子树做一个同样的处理。
这就是点分治的主要思想。
那么选哪个点作为根呢?
如果你随便选根就会出现一个问题:对于一条链而言,如果每一次选端点作为根,复杂度就是 /(O(n^2)/) 的。
因此我们需要重新选一下根。
那么怎么选根呢?树的重心是个不错的选择。
由于树的重心有个不错的性质:其最大子树大小小于等于 /(/dfrac{n}{2}/),因此每一次选取树的重心就可以做到复杂度为 /(O(n /log n)/)。
现在总结一下我们的思路:
- 首先选取树的重心为根。
- 统计经过根的所有路径对答案的贡献。
- 递归分治,重复 1,2 步骤。
点分治里面 1,3 非常板子,2 非常灵活,也是点分治的难点所在。
对于模板题而言,我们需要知道在一棵子树当中,哪些长度的路径是存在的,存在 /(book/) 数组当中。
然后假设询问长度为 /(q/),当前已经知道的存在的路径长度为 /(t/),如果 /(q-t/) 路径长度存在,那么说明 /(q/) 可以被组合出来。
为此,我们首先需要在子树中处理出所有到达根的路径的长度,然后将这些长度跟之前的长度组合,看看能不能组合出 /(q/)。
但是需要注意的是不能出现两条路径在同一棵子树出现的情况,这个看代码就好。
Code:Github CodeBase-of-Plozia P3806 【模板】点分治1.cpp
3. 总结
- 首先选取树的重心为根。
- 统计经过根的所有路径对答案的贡献。
- 递归分治,重复 1,2 步骤。
原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/245387.html