图论专题-学习笔记:点分治
1. 前言
点分治,是一种图论算法,专门用于一类树上路径统计问题。
前置知识:无。
2. 详解
2.1 树的重心
讲点分治之前我们先来讲讲树的重心。
树的重心的定义是这样的:在一棵树中,如果以一个点为根,其所有儿子的子树大小最大值是最小的,那么这个点就是树的重心。
换言之,树的重心需要满足其子树的最大值最小。
比如下面这棵树,1 号点就是树的重心。
树的重心有一个很重要的性质:其最大子树大小小于等于 。
那么怎么求树的重心呢?一遍 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 棵圈起来的子树做一个同样的处理。
这就是点分治的主要思想。
那么选哪个点作为根呢?
如果你随便选根就会出现一个问题:对于一条链而言,如果每一次选端点作为根,复杂度就是 的。
因此我们需要重新选一下根。
那么怎么选根呢?树的重心是个不错的选择。
由于树的重心有个不错的性质:其最大子树大小小于等于 ,因此每一次选取树的重心就可以做到复杂度为 。
现在总结一下我们的思路:
- 首先选取树的重心为根。
- 统计经过根的所有路径对答案的贡献。
- 递归分治,重复 1,2 步骤。
点分治里面 1,3 非常板子,2 非常灵活,也是点分治的难点所在。
对于模板题而言,我们需要知道在一棵子树当中,哪些长度的路径是存在的,存在 数组当中。
然后假设询问长度为 ,当前已经知道的存在的路径长度为 ,如果 路径长度存在,那么说明 可以被组合出来。
为此,我们首先需要在子树中处理出所有到达根的路径的长度,然后将这些长度跟之前的长度组合,看看能不能组合出 。
但是需要注意的是不能出现两条路径在同一棵子树出现的情况,这个看代码就好。
Code:Github CodeBase-of-Plozia P3806 【模板】点分治1.cpp
3. 总结
- 首先选取树的重心为根。
- 统计经过根的所有路径对答案的贡献。
- 递归分治,重复 1,2 步骤。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话