背景:这是接触的动态点分治第一题,开始不是很理解,看了很久,写了很久才理解了动态的动态所在。
前置知识:静态点分治(主要是容斥思想,一般看出来是点分治的话,想到怎么容斥剩下的操作就很机械了,还有一种LCT,目前是我的盲区),线段树(树状数组)。可以通过写一写线段树和树状数组的题来理解这两个维护的有力工具。
题目描述:给出一棵树,第一种操作:在x
点(编号,非权值)发生y
长度地震,距离x
不超过y
的点全部被摧毁,求被摧毁的权值总和;第二种操作:将x
的权值改为y
。其中摧毁独立,修改不独立。
动态点分治模板题,一开始当静态的做,已经自闭了,怎么都想不出来点分治还能修改,要不是做专题,还以为是树链剖分,可是好像又不对,苦想无果,打开百度。。
浅谈点分治:
其一:点分治本质还是分治,分治就是不断将大区间划分成小区间,小到可以简单解决再向上合并。而动态点分治和静态最大的区别就是他是动态的废话,那么要在动态中高效求解,就想到了数据结构。可是数据结构要维护,要有数据,点分治的数据(仅本题)是点权,显然维护点权即可,但是怎么维护较为合理?这时候就要牵扯到一点静态点分治的思想了,我们考虑任意点u
:①如果是初始重心(无向树中,最大子树规模最小的点),显然只会被分治0次,然后更新多次。②如果是叶子,显然会被分治多次,更新一次。③中间节点:分治次数与更新次数基于前两者中间。这里说的分治次数,指的是该点会出现在其祖先后代中的次数(显然初始重心没有确切存在的祖先,只有虚根0,故其不会出现在任何>0的编号的分治之下),更新次数指的是若该点的后代被修改,他也会被更新。(因为本题求的是和值,单点更新之后,自然向上合并的时候和值会被更新)。
其二:个人觉得最关键的一点,就是一旦一棵树你输入完了,点分治的顺序其实已经是确定的了,这也就是能用数据结构维护的原因。意思就是说每个点被分治的次数是可以求出来的,某一个点分治时的各种关系是确定的,自然向上合并的时候也是确定的。于是乎我们可以为每个点开两个树状数组(本题需要两个)。
题解:对于每个点,在getrt
之后,确定下来关系(getship
),这里的关系指的是:①他被分治的次数(ct[i]=j
, 表示i
这个点一共被分治j
次)。②他在每次被分治时,距离当前重心的距离以及当前重心(dis[i][j]=k
,i
点在第j
次被分治时距离该次重心的距离为k
,fa[i][j]=k
,i
点在第j
次分治的重心是k
)。③每个点维护两个树状数组,第一个表示其后代到他(不包括他自身)的答案(距离为下标,权值为数据,不用担心距离相同,因为本题求和值,直接相加即可),第二个表示其后代到该点父亲的答案(……与之前相同)。为何需要第二个数组?假定在x
处发生y
地震,我们用第一个算出后代的,但是祖先的结点也可能满足距离小于等于y
,故可以暴力向上调用上面结点的第一个树状数组求出答案。显然,答案是会重复的,如何扣除?(静态点分治的容斥思想),假设当前在该结点第一个树状数组中统计距离k
的答案,再向上用祖先的第一个树状数组中统计k-dis[u][i]
,最后在该结点自己的第二个树状数组中统计k-dis[u][i]
的答案,这样就可以去除冗余,画图即可证明。对于更新,比较暴力,记住每个点两个树状数组的意义,从下往上暴力更新即可。
参考博客:戳