LOJ3053 十二省联考2019 希望 容斥、树形DP、长链剖分

传送门


官方题解其实讲的挺清楚了,就是锅有点多……

一些有启发性的部分分

L=N

一个经典(反正我是不会)的容斥:最后的答案=对于每个点能够以它作为集合点的方案数-对于每条边能够以其两个端点作为集合点的方案数。原因是:对于每一种合法方案,集合点一定是树上的一个连通块,满足(n=m+1)。算点时,这种方案被算了(n)次;算边时,这种方案被算了(m=n-1)次,所以每一个方案都恰好被算了一次。

(DP):设(f_i-1)表示选择了包含(i)(i)的子树中的点的一个连通块的方案数,转移枚举每一个儿子选不选。然后设(g_i)表示以(i)为原树的根,选择了包含(i)(i)的子树中的点的一个连通块的方案数,这个在求完(f)之后换根DP,换根的时候可以同时计算出每条边的方案。

NL较小

改一下(N=L)的DP:设(f_{i,j}-1)表示选择了包含(i)(i)的子树中的点的一个连通块,其中距离(i)点最远的点与(i)距离(leq j)的方案数,(g_{i,j})表示以(i)为原树的根,选择了包含(i)(i)的子树中的点的一个连通块,其中距离(i)点最远的点与(i)距离为(leq j)的方案数。

同样可以求完(f)后换根DP求(g),但是有个问题:如何从(f_{fa_x})中去掉(f_x)的贡献。可以在计算(f)的时候计算每一个点合并所有孩子的一个前缀的答案、合并所有孩子的一个后缀的答案,这样把一个前缀和一个后缀拼起来就可以得到去掉(f_x)的贡献的(f_{fa_x})

枚举一下点和边,方案数可以直接算

52pts代码

k=1

可以枚举选择的连通块的根,用长链剖分+线段树优化,与正解关系不大所以略过

正解

(NL leq 10^7)开始。稍微修改一下(g_{i,j})的定义:设(g_{i,j})表示选择(i)、不选择(i)的子树,选择的连通块中最大距离(leq j)的方案数。

f的优化

注意到(f)可以长链剖分优化。在长链剖分的转移中我们需要支持后缀乘(因为后缀有一段要乘的值相同)、整体加(转移完成之后所有(f_{i,j})需要(+1)),可以给长链剖分打乘法标记(a)和加法标记(b)解决,一次后缀乘就把前面没有乘的部分乘上逆元。可能会有乘(0)的问题,所以还需要一个后缀赋值标记,当乘(0)时把当前的后缀赋值为(-frac{b}{a})

g的优化

(g)是一个换根DP,但是也可以用长链剖分优化。将重链和轻边的转移分开考虑:①对于重链直接暴力将轻儿子的(f)值转移过来,复杂度跟长链剖分一样;②对于轻边,假设这条轻边转移到的点(x)的子树深度为(p),那么因为在最后统计答案的时候只需要每个点的(g_{x,L}),所以(x)子树中只有(g_{x,j}(j in [L - p , L]))有用,可以只转移这一些值。那么每一条轻边就只会转移轻子树深度个(g),总复杂度也是均摊(O(n))

g的转移时的一个问题

我们还需要解决在转移(g)的过程中要取孩子的一个前缀和一个后缀的问题。可以主席树但是(O(nlogn))有点慢。考虑按照求(f)时DFS顺序的反序DFS求(g),我们到达一个点、即将递归到下一个点的时候,将这个点对于当前点的(f)的贡献消除,这样可以得到前缀的值,再拿另一个数组维护一下后缀的(f)值就可以得到前缀和后缀。支持撤销只需要在每一次从轻边修改一个点时把所有标记和将要修改的位置的值记录下来打包丢到一个栈里面。

线性求逆元

有一个预处理逆元的科技:可知要求逆元的数一定是(f_{x , p}),其中(p)(x)的子树大小,所以用(L=N)部分分的方法将所有(dp_{x,p})预处理出来,然后用类似于预处理阶乘逆元的方式预处理逆元,在更新乘法标记(a)的同时同步更新(a^{-1}),就可以做到严格的(O(n))求解。

细节非常非常的多……写题两小时调试一整天

代码

原文地址:https://www.cnblogs.com/Itst/p/10705182.html