暴力的想法是考虑钦定每个点作为到达点并统计贡献,但显然这样会算重。
注意到会算重的到达点一定构成了一个连通块,这是一个很好的性质,方便了我们容斥:我们直接用点的贡献减去边的贡献(一条边的两个端点同时是到达点)即可,因为一个连通块满足点数减边数等于 1 1 1。
先考虑点的贡献,需要统计以某个点为根且深度不超过 L L L 的连通块个数。
可以换根 DP:设 f u , i f_{u,i} fu,i 表示 u u u 子树内以 u u u 为根且深度不超过 i i i 的连通块个数, g u , i g_{u,i} gu,i 表示 u u u 子树外以 u u u 为根且深度不超过 i i i 的连通块个数,转移:(其中 s u s_u su 表示 u u u 的儿子集合)
f
u
,
i
=
∏
v
∈
s
u
(
f
v
,
i
−
1
+
1
)
g
u
,
i
=
g
f
a
,
i
−
1
∏
v
∈
s
f
a
,
v
≠
u
(
f
v
,
i
−
2
+
1
)
+
1
\begin{aligned} f_{u,i}&=\prod_{v\in s_u} (f_{v,i-1}+1)\\ g_{u,i}&=g_{fa,i-1}\prod_{v\in s_{fa},v\neq u}(f_{v,i-2}+1)+1 \end{aligned}
fu,igu,i=v∈su∏(fv,i−1+1)=gfa,i−1v∈sfa,v=u∏(fv,i−2+1)+1
注意
∏
v
∈
s
f
a
,
v
≠
u
(
f
v
,
i
−
2
+
1
)
\prod\limits_{v\in s_{fa},v\neq u}(f_{v,i-2}+1)
v∈sfa,v=u∏(fv,i−2+1) 不能写成
f
f
a
,
i
−
1
f
u
,
i
−
2
+
1
\dfrac{f_{fa,i-1}}{f_{u,i-2}+1}
fu,i−2+1ffa,i−1,因为在模意义下有可能
f
u
,
i
−
2
+
1
f_{u,i-2}+1
fu,i−2+1 为
0
0
0。
单点贡献即为 ( f u , L ⋅ g u , L ) k (f_{u,L}\cdot g_{u,L})^k (fu,L⋅gu,L)k。同时单边 ( f a , u ) (fa,u) (fa,u) 的贡献也得到了,即为 ( f u , L − 1 ⋅ g u , L ) k (f_{u,L-1}\cdot g_{u,L})^k (fu,L−1⋅gu,L)k。
然而暴力 DP 的复杂度为 O ( n L ) O(nL) O(nL) 的,需要优化。
先来处理 f f f,看到 f f f 的定义和深度有关,考虑用长链剖分优化:记 s o n u son_u sonu 为 u u u 的重儿子,让 f u f_{u} fu 继承 f s o n u f_{son_u} fsonu 的信息,再从 u u u 的其他儿子 v v v 转移过来。这里下标偏移一位的问题可以用指针处理,然后全局+1可以用 add 标记处理。
但注意到 f v f_{v} fv 的数组大小范围并不是 d v d_v dv(设 d v d_v dv 表示 v v v 子树内深度最大值),而是 0 ∼ L 0\sim L 0∼L 都有值(根据 f f f 的定义)。
但 f v f_v fv 也是有特征的: f v , d v = f v , d v + 1 = ⋯ = f v , L f_{v,d_v}=f_{v,d_v+1}=\cdots =f_{v,L} fv,dv=fv,dv+1=⋯=fv,L。于是用 f v f_v fv 向 f u f_u fu 转移时,实际上做的是前 d v + 1 d_v+1 dv+1 位暴力转移,后面的位做的是同乘 f v , d v + 1 f_{v,d_v}+1 fv,dv+1。
这可以用线段树维护,但是有一个更加美妙的做法:
- 若 f v , d v + 1 = 0 f_{v,d_v}+1=0 fv,dv+1=0,则 f u f_u fu 第 d v + 1 d_v+1 dv+1 位之后将一直会是 0 0 0,这可以打个 tag。
- 若 f v , d v + 1 ≠ 0 f_{v,d_v}+1\neq 0 fv,dv+1=0,我们打一个 f u f_u fu 全局乘 f v , d v + 1 f_{v,d_v}+1 fv,dv+1 的 tag,然后前 d v + 1 d_v+1 dv+1 位暴力转移时再乘上 f v , d v + 1 f_{v,d_v}+1 fv,dv+1 的逆。
然后就会出现很多 tag 要维护……
具体来说有以下五种:全局乘 m u l mul mul, m u l mul mul 的逆元 i m u l imul imul,全局加 a d d add add(优先级在全局乘后),重置位置 l i m lim lim( l i m lim lim 以后的位置都被重置),重置值 r e s e t reset reset(受全局乘和全局加影响)。
此时 f f f 就大致维护好了,时间复杂度 O ( n ) O(n) O(n)。
接下来是如何维护 g g g。
发现我们只需要维护 g u , L − d u ∼ g u , L g_{u,L-d_u}\sim g_{u,L} gu,L−du∼gu,L 的值,因为求 g u , L g_{u,L} gu,L 只需要用到 g f a , L − 1 g_{fa,L-1} gfa,L−1 的值。
那么同样考虑用长链剖分优化:让 g s o n u g_{son_u} gsonu 继承 g u g_u gu 的信息,而其他儿子由 g u g_u gu 暴力转移。
现在问题是对每一个儿子 v v v 求出 ∏ v ′ ∈ s u , v ′ ≠ v ( f v ′ , i − 2 + 1 ) \prod\limits_{v'\in s_{u},v'\neq v}(f_{v',i-2}+1) v′∈su,v′=v∏(fv′,i−2+1)。
显然是需要前缀和后缀拼起来的,我们这么考虑:我们让求 f f f 的时候按 d d d 从大到小枚举儿子(这样第一个枚举的一定是重儿子),并记录 f f f 的变化,然后求 g g g 的时候按 d d d 从小到大枚举儿子,前缀显然可以边枚举边求出,而后缀则可以通过不断还原 f f f 求出。
g g g 也可以用 5 个 tag 维护。
有关求逆:我们需要每次 O ( 1 ) O(1) O(1) 计算出 f u , d u f_{u,d_u} fu,du 的逆,注意到此时第二维是没有影响的,所以可以先求出所有的 f u , d u f_{u,d_u} fu,du 再离线 O ( n ) O(n) O(n) 预处理它们的逆元(每个值的逆=全部乘起来的逆×前缀积×后缀积)。
代码没写完。