图论专题-学习笔记:点分治

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 点分治

淀粉质可真好吃

P3806 【模板】点分治1


树的路径分两类:经过根的,不经过根的。

对于这些经过根的,我们可以通过一个 Solve 函数来计算这些路径的贡献。

对于这些不经过根的,我们可以分治一波,在其子树内做同样的事情,这样这些路径最后一定会经过某个根。

比如这样:

在这里插入图片描述

在统计完经过根的路径以后,删除这个根,对 3 棵圈起来的子树做一个同样的处理。

这就是点分治的主要思想。

那么选哪个点作为根呢?

如果你随便选根就会出现一个问题:对于一条链而言,如果每一次选端点作为根,复杂度就是 \(O(n^2)\) 的。

因此我们需要重新选一下根。

那么怎么选根呢?树的重心是个不错的选择。

由于树的重心有个不错的性质:其最大子树大小小于等于 \(\dfrac{n}{2}\),因此每一次选取树的重心就可以做到复杂度为 \(O(n \log n)\)

现在总结一下我们的思路:

  1. 首先选取树的重心为根。
  2. 统计经过根的所有路径对答案的贡献。
  3. 递归分治,重复 1,2 步骤。

点分治里面 1,3 非常板子,2 非常灵活,也是点分治的难点所在。

对于模板题而言,我们需要知道在一棵子树当中,哪些长度的路径是存在的,存在 \(book\) 数组当中。

然后假设询问长度为 \(q\),当前已经知道的存在的路径长度为 \(t\),如果 \(q-t\) 路径长度存在,那么说明 \(q\) 可以被组合出来。

为此,我们首先需要在子树中处理出所有到达根的路径的长度,然后将这些长度跟之前的长度组合,看看能不能组合出 \(q\)

但是需要注意的是不能出现两条路径在同一棵子树出现的情况,这个看代码就好。

Code:Github CodeBase-of-Plozia P3806 【模板】点分治1.cpp

3. 总结

  1. 首先选取树的重心为根。
  2. 统计经过根的所有路径对答案的贡献。
  3. 递归分治,重复 1,2 步骤。
posted @ 2022-04-17 17:52  Plozia  阅读(27)  评论(0编辑  收藏  举报