[R29F] 好树
数据规模:,,。
思路
对每个节点 与每个可能取值 做树形 DP。记 时:
- : 的子树内合法赋值方案数;
- :所有合法方案中,子树节点权值和的总和;
- :所有合法方案中,子树节点权值和的平方的总和。
三者均对 取模。先求后序遍历,自底向上合并。节点 固定取值 时,初始只有 自己的贡献:,,。对每个儿子 ,边约束 给出 的可取区间 。对 预处理前缀和,即可 求出区间上的:
把子树 挂到 上时,新权和等于原权和加上子树权和,于是:
( 由 对方案逐项求和得到。)
最后答案即 ,因为 恰好就是整棵树的权值和平方。
复杂度
每个节点对每个取值做常数次区间查询,而所有节点的儿子数之和为 ,故总时间复杂度 ,空间复杂度 , 时完全可行。
仓颉实现
查看仓颉实现
import std.convert.*
import std.collection.*
import std.env.*
main() {
let reader = getStdIn()
let first = reader.readln().getOrThrow().split(" ", removeEmpty: true)
let n = Int64.parse(first[0])
let P = Int64.parse(first[1])
let d = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let adj = Array<ArrayList<Int64>>(n, { _ => ArrayList<Int64>() })
for (_ in 0..(n - 1)) {
let line = reader.readln().getOrThrow().split(" ", removeEmpty: true)
let u = Int64.parse(line[0]) - 1
let v = Int64.parse(line[1]) - 1
adj[u].add(v)
adj[v].add(u)
}
// 后序遍历顺序:先子后父
let parent = Array<Int64>(n, { _ => -1 })
let order = ArrayList<Int64>()
let st = Array<Int64>(n, { _ => 0 })
var top: Int64 = 0
st[top] = 0
top += 1
while (top > 0) {
top -= 1
let u = st[top]
order.add(u)
for (v in adj[u]) {
if (v != parent[u]) {
parent[v] = u
st[top] = v
top += 1
}
}
}
let m = order.size
let post = Array<Int64>(m, { i => order[m - 1 - i] })
// f/g/h[u][x]:u 取值为 x 时,子树内合法方案数 / 权和 / 权和平方和(对 P 取模)
let f = Array<Array<Int64>>(n, { _ => Array<Int64>(n + 1, { _ => 0 }) })
let g = Array<Array<Int64>>(n, { _ => Array<Int64>(n + 1, { _ => 0 }) })
let h = Array<Array<Int64>>(n, { _ => Array<Int64>(n + 1, { _ => 0 }) })
var ans: Int64 = 0
for (i in 0..m) {
let u = post[i]
for (x in 1..=n) {
var cnt: Int64 = 1
var sm: Int64 = x % P
var sq: Int64 = (x % P) * (x % P) % P
for (v in adj[u]) {
if (v == parent[u]) {
continue
}
let dd = d[u] + d[v]
var lo = x - dd
if (lo < 1) {
lo = 1
}
var hi = x + dd
if (hi > n) {
hi = n
}
let c = (f[v][hi] - f[v][lo - 1] + P) % P
let s = (g[v][hi] - g[v][lo - 1] + P) % P
let q = (h[v][hi] - h[v][lo - 1] + P) % P
let nc = cnt * c % P
let ns = (sm * c + cnt * s) % P
let nq = (sq * c + (2 * sm % P) * s + cnt * q) % P
cnt = nc
sm = ns
sq = nq
}
f[u][x] = cnt
g[u][x] = sm
h[u][x] = sq
if (u == 0) {
ans = (ans + sq) % P
}
}
// 原地转前缀和,供父节点区间查询
for (t in 1..=n) {
f[u][t] = (f[u][t] + f[u][t - 1]) % P
g[u][t] = (g[u][t] + g[u][t - 1]) % P
h[u][t] = (h[u][t] + h[u][t - 1]) % P
}
}
println(ans)
}