点分树/动态点分治

前言

点分治是一种树上分治算法,常用于处理和路径相关的统计,或者是树上满足某种条件的点对数量。
在点分治的基础上,构造一棵支持修改的树,就成了点分树。

算法理解

点分治的核心是按照重心来划分连通块,我们在递归的时候,将上一层的重心作为这一层的重心的父亲,就成了点分树。容易知道,这棵树的树高是 O ( l o g n ) O(logn) O(logn) 的,因为总共只会有 O ( l o g n ) O(logn) O(logn) 层。下面盗用 辰星凌大佬的图 来看看点分树长什么样子。
在这里插入图片描述
如图,上面是原树,下面是点分树。可以看到,点分树和原树几乎没有任何关系。但是可以看到的是,点分树的子树点集对应原树上的一个连通块点集。
点分树有一些神奇的性质,让我们一一探讨一下:

  1. 点分树上某一个点的子树属于同一个连通块
    回忆点分治的过程,我们可以知道,当一个点作为点分树子树的根时,它在点分治的时候作为分治中心,而它在点分树上的子树对应的点集和在原树分治时对应的点集相同。
  2. 点分树所有子树大小之和为 O ( n l o g n ) O(nlogn) O(nlogn)
    这是个很奇妙的性质,是点分树用来解决问题的核心。考虑每个点对子树大小和的贡献,因为一个点最多会被 O ( l o g n ) O(logn) O(logn) 个父亲统计到,因此子树大小和为 O ( n l o g n ) O(nlogn) O(nlogn)

解决问题

点分树可以拿来解决什么问题呢?
比如求 ∑ y = 1 n d i s ( x , y ) \sum\limits_{y=1}^{n}dis(x,y) y=1ndis(x,y)。我们考虑一条路径 ( x , y ) (x,y) (x,y),我们可以设一个分界点 p p p,把路径分成 ( x , p ) , ( p , y ) (x,p),(p,y) (x,p),(p,y) 两段,然后分别维护这两段的信息。只不过,在点分树中,我们的 p p p 为分治中心,也就是 ( x , y ) (x,y) (x,y) 在虚树上的 l c a lca lca
对于任意一个 y y y,我们找到它和 x x x 在虚树上的 l c a lca lca,设为 p p p。可以知道 ( x , y ) (x,y) (x,y) 可以被划分成 ( x , p ) , ( p , y ) (x,p),(p,y) (x,p),(p,y) 两段。
因此我们可以想到一个做法, O ( l o g n ) O(logn) O(logn) 枚举 x x x 的祖先节点,记为 p p p,再来统计 p p p y y y 的贡献。而这种贡献一般通过简单的 容斥原理 求得,具体可以参照下文。

算法实现

点分树·震波

维护一棵带点权树,支持两种操作:修改 x x x 的点权,查询离 x x x 距离不超过 k k k 的点的点权和。

下面用 f a x fa_x fax 表示 x x x 在虚树上的父亲, f a t r e e x fatree_x fatreex 表示 x x x 在虚树上的祖先集合, s u b t r e e x subtree_x subtreex 表示 x x x 在虚树上的子树点集, A x A_x Ax 表示 x x x 的点权。

  1. 首先建好点分树
  2. f ( i , j ) = ∑ x ∈ s u b t r e e ( i ) , d i s ( x , i ) ≤ j A x , g ( i , j ) = ∑ x ∈ s u b t r e e ( i ) , d i s ( x , f a i ) ≤ j A x f(i,j)=\sum\limits_{x\in subtree(i),dis(x,i)\leq j}A_x,g(i,j)=\sum\limits_{x\in subtree(i),dis(x,fa_i)\leq j}A_x f(i,j)=xsubtree(i),dis(x,i)jAx,g(i,j)=xsubtree(i),dis(x,fai)jAx
    让我们先观察一下 f f f g g g 有什么不同,可以发现一个是 d i s ( x , i ) dis(x,i) dis(x,i),一个是 d i s ( x , f a i ) dis(x,fa_i) dis(x,fai)。倘若要求在 f a x fa_x fax 的子树中但不在 x x x 的子树中的点对 f a x fa_x fax 的贡献,我们用 g ( x , j ) − f ( x , j − d i s ( x , f a x ) ) g(x,j)-f(x,j-dis(x,fa_x)) g(x,j)f(x,jdis(x,fax)) 即可。
    这样,在一对查询 ( x , k ) (x,k) (x,k) 中,我们可以得到 a n s = f ( x , k ) + ∑ i ∈ f a t r e e x g ( i , k − d i s ( x , i ) ) − f ( i , k − d i s ( x , i ) ) ans=f(x,k)+\sum\limits_{i\in fatree_x}g(i,k-dis(x,i))-f(i,k-dis(x,i)) ans=f(x,k)+ifatreexg(i,kdis(x,i))f(i,kdis(x,i)),本质上是考虑 ( x , i ) , ( i , y ) (x,i),(i,y) (x,i),(i,y) 的所有 ( x , y ) (x,y) (x,y) 点对,即下图中的红色部分。
    在这里插入图片描述
    然后,插入的时候,我们 O ( l o g n ) O(logn) O(logn) x x x 的祖先 i i i,更新 f ( i ) f(i) f(i) g ( i ) g(i) g(i),查询同理。
  3. 我们接下来要考虑两个问题:
    第一,怎么求 d i s dis dis 呢,这个可以树剖求 l c a lca lca O ( l o g n ) O(logn) O(logn) 得到。其实可以使用欧拉序 + ST表来 O ( 1 ) O(1) O(1) l c a lca lca,但考虑到预处理 ST表常数太大了,就用树剖吧。有个更高效的办法是,在点分治找到重心时,直接从这个点 d f s dfs dfs,来得到每个点离祖先的距离,这样就不用在线求距离了。
    第二,怎么维护 f f f g g g 呢?注意到这是个前缀和的形式,我们可以考虑使用树状数组。但是,每个点开一个树状数组,会很白给吗?其实是不会的,考虑点分树上 i i i 的子树中的点到 f a i fa_i fai 的距离,设 i i i 的子树大小为 s z i sz_i szi,那么距离取值为 [ 1 , s z i ] [1,sz_i] [1,szi],而 i i i 的子树中的点到 i i i 的距离取值为 [ 0 , s z i 2 ] [0,\frac{sz_i}{2}] [0,2szi],而且下标是从 0 0 0 开始的,因此我们对 i i i 开一个大小为 s z i 2 + 1 \frac{sz_i}{2}+1 2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值