#3 树分治

本文介绍了树分治算法,包括点分治和边分治。点分治通过找到树的重心,将问题分解为经过根节点的路径和不经过的子树,从而降低复杂度。边分治则是通过选择边来分隔树,形成连通分量并递归处理。两种方法都在路径询问问题中应用广泛。
摘要由CSDN通过智能技术生成

1. 点分治

分治

分治算法的基本思想:
将一个规模较大的问题分解为若干个规模较小的子问题,这些子问题相互独立且与原问题性质相同,求出每个子问题后将解合并,就可以得到原问题的解。
点分治和边分治是在树形结构上进行分治的一种操作
点分治的题,边分治也可以做
主要思想就是去掉树中的某些结构(点或边),使原来的树被分解为多个不相交的部分,多用来解决 路经询问 之类的问题

经典题目

https://vjudge.net/problem/POJ-1741

给定一棵有n个点的树,树上的边有权值,问这棵树上总长度小于k的路径有多少个(1e5)
初步思路:暴力,即遍历所有的点,对于每一个点,dfs找到他和所有的点的路径长度,最后求出个数

可以考虑点分治
对于一个根结点来说,可以把树上的路径分为两种,一种是经过根节点的,一种是不经过的,(这种路径完全在一棵子树中)
(对于不经过这个根节点的子树来说,也可以看成是经过另一个根节点的路径)
基于这种分治的思想,大致的思路可以是:

  1. 找到树的根结点
  2. 计算经过根的路径
  3. 把根去掉,递归计算子树的答案(与原问题性质相同的子问题)
1. 找到树的根结点

此时可以计算一下时间复杂度,处理每一层的复杂度都会比上一层小,相加的多项式,所以只需要考虑第一层的情况,可以假设处理每一层的复杂度都是T(N)
总体复杂度=递归层数*T(N)

对于步骤一,考虑最复杂的情况

(来个图,一条链)这样的话跟暴力没有很大的区别
n次递归,降低层数

可以看到,根的选取对于时间复杂度有着重要的影响

选择根的目标:删除这个根节点之后,结点数最多的树的节点个数最小(平均)

这个点就是树的重心
性质:以树的重心为根时,所有子树的大小都不超过整棵树大小的一半(反证法证明)
假设当前树的重心为u,则整棵树的大小为size(u),对于u的任意儿子v有,size(v)<=size(u)/2.
这样每次分的时候,可以看成是分成了一半,所以递归深度就是log(n),在树是一条链的时候取到上界

(来个图,一条链)

//找重心的代码


void GetRoot(int x,int fa)
{
   
    int tmp=0;
    sz[x] = 1;
    for (int i=head[x]; i; i=e[i].nxt)
    {
   
        int v=e[i].to;
        if (v==fa || vis[v])
            continue;
        GetRoot(v,x);
        sz[x]+=sz[v];
        tmp = max(tmp, sz[v]);
    }
    tmp = max(tmp, SZ - sz[x]);//除了子树之外的部分**********
    //更新root
    if (tmp < mx)
        mx = tmp, root = x;
}
2. 计算经过根的路径

可以用dis数组,保存每个点到根的路径长度,(得到所有过跟的链)
那么,经过根的路径就可以表示为两条链拼接而成的
但是可以注意到,会存在一些不合法的情况:在同一边,所以要删除
把得到的dis数组从小到大排序之后,统计dis[i]+dis[j]<=k的个数即可

//当前子树中,每个节点到根结点的距离
void GetDis(int x, int fa)
{
   
    Q[++r] = dis[x]; //**********
    for (int i = head[x]; i; i = e[i]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值