题解:P5399 [Ynoi2018] 駄作
参考资料:周欣《浅谈一类树分块的构建算法及其应用》。
花了一天多的时间才过了这个题,写篇题解纪念一下。
前置知识: Top cluster 树分块
熟知 Top cluster 树分块的可以跳过这一个部分。
我们希望将一整棵树划分成若干个树簇,树簇是树上的一个连通块,并且一个簇内最多只有两个点与外界相连,我们称这两个点为界点。在实际运用中,我们要求其中一个界点是这个簇中深度最浅的点,称为上界点;剩下一个节点称为下界点。特别注意:一个界点可以属于若干个簇,但它最多是一个簇的下界点。
我们先考虑什么时候一个点
-
-
- 删去
u 子树中的界点以及界点的子树后,剩下点的数量超过了B 。
通过如上的构造方式,我们可以使得界点的数量不超过
我们再考虑如何划分簇,我们考虑以任意顺序遍历
- 加入新子树的未划分点后,
S 的大小将会大于B 。 - 加入新子树的未划分点后,
S 将会有多于1 个下界点,违背了条件。 - 在先前我们没有处理
u 的任意子树。
我们可以发现,这样划分的每一个簇的大小都不会超过
上述内容大部分来源于论文,但是我们应该注意到这样的划分方式有以下性质:
- 一个簇中最多有两个界点,一个簇外的点到达簇内的点必须经过这两个点的其中一个。
- 一个簇的上界点是这个簇中深度最小的点,这极大地简化了某些计算问题。
- 由于(2)的特性,我们可以建立一棵收缩树,将每一个簇的上界点和下界点连边,这一棵树的大小是
O(\frac{n}{B}) 的,很适合我们进行计算。
Solution
我们可以将题目中一个点的邻域拆分成每一个簇的邻域,并且由于 Top Cluster 的特殊性质,大部分的簇的邻域中心都是界点,只有
因此,我们可以将贡献分成簇间贡献和簇内贡献分别处理,考虑计算所有的贡献:
-
不同簇之间的贡献。由于两个簇中的点之间的路径经过固定的点,我们可以在收缩树上进行树形DP来统计贡献,考虑当前点是
u :- 点
x 和点y 的贡献:我们可以预处理出点x 到x 所属簇下界点的距离dl_x ,因此x 到y 的距离可以写成dl_x + dep_y - dep_u 。 - 点
y 和点z 的贡献:可以写成dep_y + dep_z - 2dep_u 。
因此,我们可以处理对于每一个块,两个点对应的邻域的大小,深度和,到下界点的距离和就可以转移了,这里总共的时间复杂度是
O(m (B + \frac{n}{B})) 。 - 点
-
一个簇内两个邻域之间的贡献,并且两个邻域的中心都是界点,考虑对于每一个块预处理所有的答案,
f_{0/1,0/1,i,j} 表示第一个邻域是以上/下界点为中心,半径为i ,第二个邻域是以上/下界点为中心,半径为j ,暴力统计即可,每一块的时间复杂度是O(B^2) ,需要做二维前缀和,这里的时间复杂度为O(\frac{n}{B}(B^2 + m)) -
一个簇内两个邻域之间的贡献,两个邻域的中心至少一个不是界点,对于对于每一个询问只会有
O(1) 个块会这样,因此O(B) 地计算,考虑拆贡献dist(a,b) = dep_a + dep_b - 2 dep_{LCA(a,b)} ,前两项统计是容易的,后一项可以对于第一个邻域每个点到根(这里是上界点)的路径+1 ,查询第二个每一个点到上界点的路径和,可以较为轻松的做到O(B) 处理。这里的时间复杂度是O(mB) 。
不妨设
Optimization
- 块长取
B = 2\sqrt{n} 比较优秀。 - 合理调整多维数组的顺序。
- 这里需要
O(1) 求dist(a,b) ,这个东西很慢,我们可以考虑预处理出每一个簇内的点到上下界点的距离,减少调用dist(a,b) 的次数。 - 避免使用 DFS,改成循环效率更高。
- 尝试找到一种顺序,使其是原树的dfs序,并且每一个簇的访问是连续的,这可以考虑在做 Top Cluster 的时候“以任意顺序遍历子树”改成先遍历没有界点的子树,后遍历有界点的子树,这样就可以了,可以看图理解一下。
Code
代码常数较大,极限卡过仅供参考 查看代码。