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

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

1. 前言

点分治,是一种图论算法,专门用于一类树上路径统计问题。

前置知识:无。

2. 详解

2.1 树的重心

讲点分治之前我们先来讲讲树的重心。

树的重心的定义是这样的:在一棵树中,如果以一个点为根,其所有儿子的子树大小最大值是最小的,那么这个点就是树的重心。

换言之,树的重心需要满足其子树的最大值最小。

比如下面这棵树,1 号点就是树的重心。

在这里插入图片描述

树的重心有一个很重要的性质:其最大子树大小小于等于 n 2 \dfrac{n}{2} 2n

那么怎么求树的重心呢?一遍 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 ) O(n^2) O(n2) 的。

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

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

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

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

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

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

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

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

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

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

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

3. 总结

  1. 首先选取树的重心为根。
  2. 统计经过根的所有路径对答案的贡献。
  3. 递归分治,重复 1,2 步骤。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值