树分治总结

· · 个人记录

点分治

点分治用于解决树上路径问题。

适用范围:可以在较快时间(比如 O(n)O(n\log n))内求出经过某个点 x 的所有路径的信息。

算法流程:

每次选择当前树的重心 r 作为根节点,处理经过 r 的所有路径的信息。

然后对 r 的每一个子树递归处理。

复杂度证明:

由重心性质,每次递归子树大小至少减半,所以每个点只会在 \log 个子树中出现,总复杂度就是 O(n\log n)

再考虑处理路径信息的复杂度,根据主定理,如果处理大小为 n 的子树复杂度是 O(n\log^k n),总复杂度就是 O(n\log^{k+1}n)。如果是 O(n\sqrt n),总复杂度还是 O(n\sqrt n)

模板题:P3806 【模板】点分治1 提交记录

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+3,P=1e7+3;
int he[N],to[N*2],ne[N*2],len[N*2],st[N],sz[N],q[109],o,r,t,n,m;
bool ans[109],f[P],b[N];
void gr(int x,int y){//求重心
    int i=he[x],j,w=0;
    for(sz[x]=1;i;i=ne[i])if(!b[j=to[i]]&&j!=y)gr(j,x),sz[x]+=sz[j],w=max(w,sz[j]);
    if((w=max(w,n-sz[x]))<o)o=w,r=x;
}
void gd(int x,int y,int z){if(z<P&&(st[++t]=z))for(int i=he[x],j;i;i=ne[i])if(!b[j=to[i]]&&j!=y)gd(j,x,z+len[i]);}
void wk(int x){
    int i=he[x],j,k,l=0;
    for(b[x]=1;i;i=ne[i])if(!b[j=to[i]]){
        for(gd(j,x,len[i]),j=t;j>l;--j)for(k=1;k<=m;++k)if(q[k]>=st[j])ans[k]|=f[q[k]-st[j]];
        while(l<t)f[st[++l]]=1;
    }
    while(t)f[st[t--]]=0;
    for(i=he[x],l=n;i;i=ne[i])if(!b[j=to[i]])n=sz[x]>sz[j]?sz[j]:l-sz[x],o=N,gr(j,0),wk(r);
    //n是子树j的size,因为x和j在求sz时一定是父子关系,所以分两种情况讨论即可求出n
}
int main(){
    int i,j,k,l,t=0;
    scanf("%d%d",&n,&m);
    for(i=f[0]=1;i<n;++i)scanf("%d%d%d",&j,&k,&l),ne[++t]=he[j],to[t]=k,len[t]=l,he[j]=t,ne[++t]=he[k],to[t]=j,len[t]=l,he[k]=t;
    for(i=1;i<=m;++i)scanf("%d",q+i);
    for(o=N,gr(i=1,0),wk(r);i<=m;++i)puts(ans[i]?"AYE":"NAY");
    return 0;
}

习题:

P6626 [省选联考 2020 B 卷] 消息传递

P7518 [省选联考 2021 A/B 卷] 宝石

P5306 [COCI2019] Transport

P4183 [USACO18JAN]Cow at Large P

点分治统计答案常见方法

$2.$ 先加入所有子树的信息,然后统计每棵子树的答案,再容斥一下减掉每棵子树到自己的答案。点分树的题目经常使用这种方法。 $3.$ 每次取出当前最小的两棵子树,统计它们之间的答案,然后合并它们的信息,作为一棵新的子树,直到当前分治范围内只剩一棵子树。如何保证取出的子树最小?可以像合并果子一样用堆维护子树大小。 方法 $3$ 的优势在于每次只统计两棵子树之间的答案,通常可以代替边分治,而且还不需要向边分治一样考虑虚点和虚边的权值。假定合并两棵子树 $x,y$ 的时间复杂度是 $O(size_x+size_y)$,这样做的时间复杂度是 $O(n\log n)$,证明如下: 首先考虑堆部分的复杂度。 点分治有一个显然的性质:分治过程中子树的总个数是 $n$ 个。 证明:每次分裂出一棵新的子树 $j$,相当于断掉了 $j$ 到分治中心 $r$ 的边。原树有 $n-1$ 条边,加上最开始的子树,总个数即为 $n$ 个。 每次合并两棵子树只会进行常数次堆的操作,复杂度即为 $O(n\log n)$。 然后是合并子树的复杂度。因为 $size_x+size_y\leq 2\times\max(size_x,size_y)$,所以只需要考虑小子树向大子树合并时大子树的贡献。 一棵子树在点分治的同一层,经过两次小子树向其合并的操作之后,$size$ 至少变为原来的两倍。 假设第一次是 $y$ 向 $x$ 合并,第二次是 $z$ 向 $x$ 合并。因为每次取出的都是最小的两个子树,所以 $size_x\leq size_z$,所以 $2\times size_x\leq size_x+size_y+size_z$。 还有一种情况,是这棵子树进入了点分治的上一层,然后一棵小子树向其合并。因为点分治层数是 $\log n$,所以这样的合并也至多有 $\log n$ 次。 综上,对于每一个点,只会在 $\log n$ 次合并中作为大子树的结点被访问到。 所以合并果子点分治的复杂度为 $O(n\log n)$。 ## 边分治 对于一些问题,合并多个子树比较麻烦,但合并两个子树较为简便,可以用边分治或合并果子点分治解决。 算法流程: 类似点分治,不过分治中心是边不是点,然后处理经过这条边的所有路径信息。之后递归两个子树。 直接边分治的复杂度可以用菊花卡到 $O(n^2)$,所以要三度化。 三度化常见有两种方式。 1.把所有儿子依次加一个虚点串起来。 2.像线段树一样递归建虚点。 可以参考这篇[博客](https://www.cnblogs.com/Miracevin/p/10430208.html)中的图片。 建出的虚点和虚边需要按题目要求赋上合适的权值。 复杂度分析: 首先要知道一个结论:一棵树中每个点度数不超过 $d$,那么一定存在一条边分出两个子树大小均属于 $[\dfrac{n}{d+1},\dfrac{nd}{d+1}]$。 证明:假设不存在。考虑一个点 $x$ 满足 $x$ 的子树大小大于 $\dfrac{nd}{d+1}$,$x$ 的所有儿子子树大小小于 $\dfrac{n}{d+1}$,因为儿子数不超过 $d$ 个,所以总大小不可能大于 $\dfrac{nd}{d+1}$,矛盾。 有了结论以后,边分治的复杂度易知为 $O(n\log n)$,分析和点分治类似不再赘述。 实现时找到一个 $x$ 满足 $\max(sz_x,n-sz_x)$ 最小($n$ 为当前分治子树的大小),将 $(x,fa_x)$ 作为分治中心边即可。 在部分问题中,边分治(或者合并果子点分治)比点分治拥有更优秀的复杂度。 以习题第一道为例,用点分需要树状数组维护只能 $O(n\log^2 n)$。用边分直接实现需要对边权排序,注意到权值只有 $01$,可以用双端队列 bfs 代替 dfs 求距离,这样求出的边权数组就已经排好序了,就可以 $O(n\log n)$。 习题: [bzoj#2870. 最长道路tree](https://darkbzoj.tk/problem/2870) [提交记录](https://darkbzoj.tk/submission/121927) [P5114 八月脸](https://www.luogu.com.cn/problem/P5114) [P4220 [WC2018]通道](https://www.luogu.com.cn/problem/P4220) ## 点分树 点分树用于解决需要多次修改查询的树上路径问题。 构建:对原树点分治,每一层的分治中心向下一层的分治中心连边。 性质: 有 $n$ 个结点 $n-1$ 条边,证明见上文“点分治统计答案常见方法”。 点分树的树高是 $\log n$,通过点分治的复杂度分析得出。因此在点分树上可以暴力跳 fa。 点分树上两点的 lca 一定在这两个点的路径上。根据点分树和 lca 的定义,分治中心为 lca 时这两个点都在分治范围内,且位于不同子树。 注意事项: 点分树的题目通常要求一个点到祖先的距离,但这个距离不能在跳 fa 时累加求出。 正确的做法是对每个点开一个数组表示到每个祖先的距离。在点分治的过程中求出分治中心到分治范围所有点的距离并记录。 模板题:[P6329 【模板】点分树 | 震波](https://www.luogu.com.cn/problem/P6329) [提交记录](https://www.luogu.com.cn/record/49559820) 做法: 对每个点开树状数组用来维护分治范围内距离不超过 $k$ 的价值和。 修改和查询暴力跳 fa 更新/查询树状数组即可。 在 $f_{x,i}$ 的树状数组上查询时 $f_{x,i-1}$ 的子树会算重,需要对每个点再开一个树状数组用来容斥。 树状数组用 basic_string/vector 动态开空间,只开到子树距离最大值,这样总空间就是 $n\log n$。 时间复杂度 $O(n\log^2 n)$,空间 $O(n\log n)$。 习题: [SP2666 QTREE4 - Query on a tree IV](https://www.luogu.com.cn/problem/SP2666) [SP2939 QTREE5 - Query on a tree V](https://www.luogu.com.cn/problem/SP2939) [P3241 [HNOI2015]开店](https://www.luogu.com.cn/problem/P3241) [P3345 [ZJOI2015]幻想乡战略游戏](https://www.luogu.com.cn/problem/P3345) [CF936E Iqea](https://www.luogu.com.cn/problem/CF936E) ### 动态点分树 在点分树的基础上,增加新建结点并连边的操作。 加入点 $x$ 和边 $(x,y)$ 时,可以先将 $x$ 在点分树上的父亲置为 $y$。 但是这样得到的点分树可能不平衡。可以用类似替罪羊树的方式,如果 $sz_x>\alpha\times sz_{fa_x}$,就对 $fa_x$ 的子树重新建树,这里 $fa$ 表示点分树上的父亲。$\alpha$ 取 $0.8$ 左右即可。这样可以保证树高为 $\log n$。 每加入一个点,就跳 $fa$ 更新信息。如果经过的点存在不平衡的点,就从根节点开始向下找到第一个不平衡的点,然后重构这个点的子树。 习题: [P3920 [WC2014]紫荆花之恋](https://www.luogu.com.cn/problem/P3920) [题解](https://www.luogu.com.cn/blog/221955/solution-p3920) [P6541 [WC2018]即时战略](https://www.luogu.com.cn/problem/P6541) [题解](https://www.luogu.com.cn/blog/221955/solution-p6541) ## 边分树 ### 边分树合并 习题:[P4565 [CTSC2018]暴力写挂](https://www.luogu.com.cn/problem/P4565) ### 可持久化边分树 习题:[CF757G Can Bash Save the Day?](https://www.luogu.com.cn/problem/CF757G) ## 静态链分治 [树上启发式合并(dsu on tree)](https://www.luogu.com.cn/blog/221955/shu-shang-qi-fa-shi-ge-bing-dsu-on-tree) ## 动态链分治 [全局平衡二叉树](https://www.luogu.com.cn/blog/221955/quan-ju-ping-heng-er-cha-shu) 参考: [ 一种基于错误的寻找重心方法的点分治的复杂度分析 lca](https://liu-cheng-ao.blog.uoj.ac/blog/2969)