洛谷 P5291 [十二省联考2019]希望
https://www.luogu.com.cn/problem/P5291
https://www.luogu.com.cn/problemnew/solution/P5291
一个有 (n) 个节点的树,一共有 (k) 个救援队,每个救援队有一个救援范围,即一个连通块 (s_i)
我们称一个节点 (u) 可被第 (i) 个救援队到达当且仅当
- (u in s_i)
- (forall v in s_i, dis(u, v) le L)
求有多少方案 ({ s_1, cdots, s_k }) 满足存在一个节点可被所有救援队到达
两种方案不同当且仅当存在 (i) , 使得 (s_i ot= s'_i)
答案对 (998244353) 取模
(1 le n le 10^6)
(0 le L le n)
(1 le k le 10)
Tutorial
由于对于一种合法的方案,可以选择的点构成了一个连通块,我们可以利用树上 点数-边数=1 这一特点,将对于每个连通块统计答案转化为对于每个点和边统计答案.
设 (f(u, i)) 表示 (u) 的子树中包含 (u) 且所有点距离 (u) 不超过 (i) 的连通块个数 +1
设 (g(u, i)) 表示包含 (u) 且不包含 (u) 子树中的点且所有点距离 (u) 不超过 (i) 的连通块个数
每个点的贡献为
每条边的贡献为
直接DP的复杂度为 (O(nL)) ,接下来我们考虑如何优化计算 (f,g) 的过程
由于第二维与深度有关,所以考虑使用长链剖分优化.
(f) 的优化就是经典问题 , 需要进行所有值 +1 的操作,维护一个加法标记.
对于 (g) 我们需要从上到下更新,对于 (u) 节点,我们需要知道的只有 (g(u, L)) ,所以我们只需要保留 $g(u, L - len(u) + 1) $ 到 (g(u, L)) 的DP值,其中 (len(u)) 表示 (u) 出发的长链的点数.
对于上边 (g) 的转移中的 (prod) 的部分,其实也就是 (dfrac {f(fa, i - 1) - 1}{f(v, i - 2)}) 但是 (f(v, i - 2)) 可能等于 (0) ,我们可以看作 (f(v, i - 2)) 序列的一个前缀和一个后缀的乘积.
一个前缀的积可以表示为在计算 (f) 的过程中某一时刻的 (f(fa, i - 1)) ,为了得到这个值,我们可以在计算 (f) 每次转移时纪录修改过的值,就可以实现回溯.
一个后缀的积,可以在求 (g) 的时候采用与计算 (f) 时相反的顺序,类似维护 (f) 的方法维护.
注意由于 (f,g) 的定义中都有 不超过 ,以 (f) 为例, 设 (l_u = len(u) - 1),实际上 (f(u, k), k > l_u) 也是有值的,且等于 (f(u, l_u)) , 因此转移时 (f(u)) 的一段后缀会乘上 (f(v, l_v))
当 (f(v, l_v) = 0) 时相当于将一段后缀赋值,可以维护 (pos) 表示 (f(v, k), k ge pos) 的值都等于 (0)
当 (f(v, l_v) ot=0) 时,由于后缀之外的部分是 (O(l_v)) 的,所以我们可以将那段前缀乘上 (f(v, l_v)) 的逆元,然后维护一个乘法标记.
由于 (n le 10^6) ,所以我们想办法去掉求逆元带来的 (O(log n)) , 发现我们只需要求出所有 (f(u, l_u)) 即 (u) 子树中所有包含 (u) 的连通块的个数, 可以用类似求阶乘逆元的方法求出这 (O(n)) 个数的逆元.
总时间复杂度 (O(n))
Summary
首先利用 点数-边数=1 将统计连通块转化为统计点.
写出DP方程后,发现第二维与深度有关,于是考虑长链剖分.
由于只关心 (g(u, L)) , 所以从上到下的部分也可以使用长链剖分
通过纪录每次修改的位置,来实现回溯.
对于全局加,后缀乘,后缀赋值,利用标记在复杂度不劣化的条件下维护.
求 (n) 个数逆元可以用类似求阶乘逆元的方法做到 (O(n))