(2023-10-30编写)【CSP202309-5】阻击-动态点分治+一堆数据结构(无代码)

测试地址: 阻击

题目大意: 有一棵 n n n个节点的无根树,每条边有边权,有 m m m次修改,每次永久修改一条边的边权,求所有修改前和每次修改之后,树中所有路径边权和的最大值。

做法: 本题需要用到动态点分治+一堆数据结构。

这道题题意简单得像是经典模板题,可能还真是,只不过它是作为“动态点分治”这种东西的模板而存在的。

如果没有修改,那么“求树中所有路径边权和的最大值”这种对树的所有路径作一个统计的问题,很显然适合用点分治解决。带修改的话,那就需要“动态点分治”,其实就是用点分治的结构(又称“点分树”)性质,外加其它数据结构的帮助,来动态维护点分治的过程和结果。

先看看没有修改的话,点分治要怎么做。对于分治到的一棵子树,首先求出重心,重心删掉后分成的子树是分治的下一步,而在当前这个位置我们要求的是,子树中所有过重心的路径边权和的最大值。我们可以以重心为根再遍历一遍这棵子树,求出从重心出发到子树中各点的路径边权和,然后,在重心这个位置,我们需要选择两个路径边权和最大的“方向”,在这两个方向上各有一个使得路径边权和最大的端点,把它们连接在一起之后,整条路径的边权之和最大。注意这个“方向”也包括不往下延伸的那个“方向”,因为重心自己也可以是端点。假设子树大小是 s i z e size size,求重心的复杂度是 O ( s i z e ) O(size) O(size),后续算法的复杂度也是 O ( s i z e ) O(size) O(size),由点分治的性质可知,整个点分治的复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

在有修改的情况下,最开始的一次点分治肯定是需要做的,而对于之后的修改,我们肯定要用数据结构来维护一些数据,尽可能少进行重复计算。首先,对于一次修改,我们发现它最多影响到 log ⁡ n \log n logn棵分治子树的答案,因为我们可以找到包含该边的最小分治子树,只有包含它的分治子树答案才有可能变化,由于点分治的性质,包含它的分治子树就只有它在点分树上的祖先,而点分树的深度不超过 log ⁡ n \log n logn,得证。点分树和包含每条边的最小分治子树都可以在最开始的点分治中预处理出来,后续查找时可以做到 O ( 1 ) O(1) O(1)复杂度。

对于每棵可能受影响的分治子树,“从重心出发到子树中各点的路径边权和”这点在最开始进行的点分治中可以求出,而对于每次修改,我们发现受到影响的范围都是以重心为根的情况下的一棵子树,而且变化的值是同一个常数(新边权和原边权的差值),于是想到先将所有点按DFS序排列,这样一棵子树就和DFS序上的一个区间对应,而区间修改,以及之后要做的区间最值查询(查询某个“方向”,也就是某棵子树内,路径边权和的最大值),这些都提示了需要用“线段树”来处理这些数据。DFS序,每条边影响的子树和对应的DFS序范围,最初的建树,这些也都可以在最开始的一次点分治中预处理以做到之后的 O ( 1 ) O(1) O(1)查询,而之后所有操作的复杂度合起来是 O ( log ⁡ s i z e ) O(\log size) O(logsize)的。

而在重心处合并路径时,每次修改最多会改变一个“方向”上的答案,所以我们对所有“方向”的答案维护一个multiset,而每次修改,我们在修改的“方向”的子树上做一个区间查询来获得新的答案,然后将原答案从multiset中删去,并插入新的答案,最后合并时,取出multiset中最大的两个元素相加即可。所有这些操作的复杂度合起来也是 O ( log ⁡ s i z e ) O(\log size) O(logsize)的,即使常数可能比较大。

这样一来,每次修改的时间复杂度是 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n),因为影响到 log ⁡ n \log n logn棵分治子树,而每棵分治子树内又要进行 O ( log ⁡ s i z e ) ≤ O ( log ⁡ n ) O(\log size)\le O(\log n) O(logsize)O(logn)的维护操作。这样一来,解法整体的复杂度为 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n),理论上应该可以解决这道题,如果常数不是特别大的话。不过我也没写,我也不清楚。

你可能感兴趣的:(数据结构,算法)