[R68D] 树的高度
- 难度 普及/提高-
- 时限 1s
- 空限 512m
- 树树上 DP
数据规模:,。
思路
要求任意祖先 与后代 ()满足 。由于高度关系具有传递性,只要每条边都满足父节点高度大于子节点高度,任意祖先—后代对就自动满足;反过来,祖先—后代对包含父子边,所以约束等价于:对树上每条边 ( 是 的儿子),。
高度只能增加,设 为 最终需要达到的最小高度。叶子没有儿子,直接取 ;内部节点 要同时高过所有儿子,故
自底向上(后序)算一遍即可,答案即 ,累加过程中无需额外数组存 :处理节点 时,用 记录儿子们贡献的 ,则 ,算完后用 去更新父亲的 。
树是链时 最大约为 ,答案最大约 ,用 Int64。
实现上用迭代 DFS 求先序序列(避免 深度的递归爆栈),先序序列的逆序就是合法的后序处理顺序——先序中父节点总在子孙之前,逆序时子孙先被处理。
复杂度:时间 ,空间 。
仓颉实现
import std.env.*
import std.convert.*
import std.collection.*
main(): Int64 {
let reader = getStdIn()
let n = Int64.parse(reader.readln().getOrThrow())
let h = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let adj = Array<ArrayList<Int64>>(n + 1, { _ => ArrayList<Int64>() })
for (_ in 1..n) {
let e = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let u = e[0]
let v = e[1]
adj[u].add(v)
adj[v].add(u)
}
// 迭代 DFS 得到先序序列(避免递归爆栈)
let parent = Array<Int64>(n + 1, { _ => 0 })
let order = ArrayList<Int64>()
let stack = ArrayList<Int64>()
stack.add(1)
while (stack.size > 0) {
let u = stack.remove(at: stack.size - 1)
order.add(u)
for (v in adj[u]) {
if (v != parent[u]) {
parent[v] = u
stack.add(v)
}
}
}
// 逆先序即合法的后序处理顺序:g[u] 存 max(儿子最终高度 + 1)
let g = Array<Int64>(n + 1, { _ => 0 })
var ans: Int64 = 0
var i = order.size - 1
while (i >= 0) {
let u = order[i]
var fu = h[u - 1]
if (g[u] > fu) {
fu = g[u]
}
ans += fu - h[u - 1]
if (u != 1) {
let p = parent[u]
let t = fu + 1
if (t > g[p]) {
g[p] = t
}
}
i -= 1
}
println(ans)
return 0
}