点分治总结
文章目录
一直想总结点分治的一些技巧。结果因为各种原因鸽了。现在补一个。
点分治与点分树
点分治(centroid decomposition)算法的大致流程是,对于个点无根树:
- 首先找到的一个重心,然后处理与重心有关的信息。
- 然后删掉重心,那么将划分为若干个更小的连通块,对于每个连通块递归进行上述算法。
如果我们把的重心当作的个儿子,递归构建,这样可以建出一棵新的树,我们称之为点分树(centroid decomposition tree)。
点分树的重要性质:由于是的重心,因此。也就是说每往下走一层,连通块大小至少减半。因此点分树的树高是的。
那么点分治和点分树适用于处理什么类型的问题呢?
路径问题
需要求出满足某种条件的路径条数,或者满足某种条件的路径权值和。那么我们可以在点分治的过程中,将路径按照
- 经过重心(或者以重心为端点)
- 不经过重心
分类。如果一条路径不经过重心, 它一定在某个更小的连通块中,这类路径我们递归处理。因此在当前状态下我们只用处理经过重心的路径。也就是说,点分治将任意路径转化为了经过定点的路径。如果我们把重心作为连通块的根结点,那么点分治还可以把无根树路径转化为有根树上经过根结点的路径。
点集问题
有时我们需要求出,满足某种条件的连通块(点集)的个数。这个条件通常是可以转化为点与点之间的条件的。那么我们可以钦定在连通块(点集)深度最低的点上统计这个连通块的信息。
具体地,我们仍然考虑点分治。那么我们将连通块分成
- 经过重心(如果是点集,那么就是跨过重心)
- 不经过重心
两种。以重心为根,那么相当于我们要求的就是包含根结点的连通块的信息。
其他类型
事实上,点分治可以运用的场景还有很多。但它的主要作用就是钦定一个点,把问题转化为和这个点有关的信息处理。
记号
为了方便表述,我们在这里定义一些记号。
一棵无根树,其中是点集,是边集。表示一条无向边。
表示到的一条无向路径,表示到的有向路径。
的点分树是。
以结点为重心的点分树连通块记作。在点分树上的父亲是。
捉迷藏
给你一棵树个点的树,支持个询问:
- 改变一个点的颜色(黑白)。
- 求黑点构成的虚树的直径。
。
如果没有修改操作,那么可以点分治或者直接树形 DP 求。
我们从修改操作的角度考虑。建出点分树。那么修改一个点的颜色,会影响包含这个点的连通块的信息。而点分树树高是的,因此包含这个点的点分树连通块只有个。那么我们只需要思考,如果使用点分树连通块维护信息即可。
一条路径(直径),一定经过某个点分树连通块的重心。同时,一条路径可以拆分成从重心出发的两条路径,分别通往两个不同的子连通块。
因此对于点,我们使用一个堆表示中的黑点到的直径(不管是不是黑点)。然后我们用另一个堆维护的点分树儿子,且是黑点的的堆顶。这样,的最大值和次大值就是中经过的直径。然后对于每个点,我们把它的直径都放在一个大的堆中。那么的堆顶就是整颗树的直径。
对于一次修改的颜色,我们可能会涉及到包含的点分树连通块的信息改变。那么我们暴力跳父亲,修改对应的以及即可。
那么我们维护的是带删除的堆。
还有一个小优化。就是我们预处理表示点到的点分树的级父亲的距离。
总时间复杂度。
幻想乡战略游戏
给一棵阶的点边带权的树,初始时点权为。有次操作,每次修改某个点的点权,要求在每次操作后询问
其中是点权,表示两点的距离(路径长度)。
特殊性质:每个点度数不超过。
。
设。可以发现,在固定的条件下,是一个树上凸函数,且以为顶点。
这里的树上凸函数是指对于每条路径上的点,是一个凸函数。实际上在本题中它是一个绝对值函数,但也满足凸函数的性质。
因此若干个凸函数的和也是凸函数。也就是说也是树上凸函数。那么我们就可以使用类似找重心的算法来求。假设当前结点是,那么我们枚举在原树上的邻居,然后计算。如果,那么我们就跳到的儿子连通块中包含的那个的重心上,递归下去。
因此现在的问题是,如何在递归的过程中快速计算。
不方便直接维护,我们就维护它的贡献和。当我们从跳到——子连通块的重心的时候,其他连通块就可以缩点,然后接在的某个叶结点上。因此我们可以统计其他连通块的贡献和,然后把点权和加到这个叶子结点上。因此我们只需要维护即可,当然还有一些其他需要维护的,比如之类的,但这都是小问题。
在修改的时候,这些可以暴力跳父亲修改。
时间复杂度。
SCOI Online 树
是 SCOI2018 的题。
给出一棵个点的树,第个点有权值,现在需要支持次操作:
- 第一种操作格式为 “1”,表示询问从出发的简单路径,经过的点权值之和的最大值;
- 第二种操作格式为 “2”,表示将修改为。
。
建出点分树。从出发的路径,一定是经过点分树上的某个父亲,然后延伸向另一个与不同的连通块。修改一个点的权值,会修改若干条路径。不过我们已经将问题转化为了点分树连通块里以重心为端点的路径信息问题——也就是定了根。那么修改一个点权,就会修改一个子树的路径的权值。自然想到线段树维护子树 max。再用一个堆维护子连通块的 max 集合即可。修改的时候暴力跳父亲修改即可。
时间复杂度。
Giant Penguin
定义仙人掌为一个简单无向连通图,满足其中的任意一个点都被包含在个简单环中。
给你一个个点条边的仙人掌,操作:
- 标记一个点;
- 问距离点最近的被标记的点的距离。
。
考虑在仙人掌上点分治。我们先搞一棵生成树。对于当前连通块,我们确定一个点为重心,那么至多有条非边是连接在子连通块之间的。而且我们可以快速找出这条边。那么在删除的时候,我们同时删除重心和这条边,就可以把其他子连通块割裂开来。这样就可以建出仙人掌的点分树。我们称这条边的端点集合是的关键点。
距离最近的被标记的点,要么经过的点分树上某个父亲,要么经过某个父亲的关键点。因此我们可以对于每个点预处理表示到点分树的级父亲的第个关键点的距离。然后维护表示距离的第个关键点的最小距离(不妨设为)。因为我们只会标记而不需要撤消,因此不需要维护堆。这样一通更新即可。
时间复杂度。
Airplane Cliques
给你一棵个点无根树。给出常量。
称一个点集是好的,当前仅当其中任意两个点距离不超过(边长为)。
求出大小为的好的集合的个数。
,对取模。
任意两个点距离不超过,等价于给树定根后:
- 设是中深度最深的结点;
- 对于任意,到的距离不超过。
因此我们可以把放在深度最深的点处统计方案数。
为了防止多个深度相同的最深点重复统计,我们按序给结点标号来统计。也就是说我们给树定根,按照 BFS 序
- 标记一个结点;
- 统计有多少个标记过的结点到的距离不超过。
由于是 BFS 序,这样就不用考虑深度的问题了。因此给树定根只是为了确定 BFS 序。实际上这个统计是可以看作在无根树上进行的。
至于统计的部分,我们可以维护点分树连通块中,到重心的标记过的点的距离集合。那么我们查询就暴力跳重心,二分统计即可(要减掉在自己连通块部分的贡献)。可以使用点分树 + 树状数组统计。
这样统计完之后,设表示的统计结果。设表示大小为的好的集合的个数。显然对的贡献是。不防设。则对的贡献就是。因此
可以得到。这是一个卷积的形式,可以简单 reverse 一下就能多项式计算了。
因此总时间复杂度。
修订记录
- 2020年5月8日 创建文章