O(n^2) 树形背包
(zzs讲课没听, vp遭殃)
树形背包首先压了一维, 这个要注意.
设 $dp(u, k)$ 为在以 $u$ 为根的子树中选 $k$ 个点, 且一定选 $u$ 这个点, 子树的最大价值.
代码长这样(这个板子不完全正确的):
|
|
注意过程中没有用到非法状态 $dp(u, 0)$.
注意代码中对 $dp(u, m)$ 的第二维并没有全部枚举, 而是枚举考虑过的子树总大小. 所以, 枚举 $k$ 的上限是前 $i$ 个子树的总大小加 $1$ (点 $u$), 然后再枚举当前子树 $v$ 选的个数 $x$, 枚举量为子树大小.
这样复杂度是 $O(n^2)$ 的.
证明:
两个循环, 都是子树大小的枚举量. 可以把两个树看成两个子树中的两个点. 所以总共枚举的点对个数就是总枚举量. 然后一个点对仅会在 lca 处枚举到. 而点对的 lca 唯一, 所以每个点对仅被枚举一次, 故复杂度就是总点对个数, 即 $O(n^2)$
南京那题:
树, 每个点有一个怪, 一个怪有血量 $hp_i$, 开局你可以不用花费代价选择 $m$ 个怪直接消灭掉, 然后 需要按从父亲到儿子的顺序消灭其他怪物, 代价为
$$hp_u + \sum_{v \text{上有怪物}} hp_v$$
可以这么考虑, $dp(u, k, 0/1)$ 为以 $u$ 为根的子树, 选了 $k$ 个, 其中 $u$ 是不是活着的, 活着必选, 死了就等于预先把它干掉了.
初始条件 $dp(u, 0, 0) = 0$ (先干掉), $dp(u, 1, 1) = hp(u)$ (活着, 选且必选这个点), 其他为无穷.
转移:
$$dp(u, k, 0) \leftarrow dp(v, k, 0)$$ $$dp(u, k, 0) \leftarrow dp(v, k, 1)$$
上面两个是 $u$ 先被干掉了, 所以不需要多余的代价, 直接儿子转移.
$$dp(u, k, 1) \leftarrow dp(v, k, 0)$$
上面这个是 $u$ 活着, 儿子 $v$ 被干掉了, 不需要多余代价.
$$dp(u, k, 1) \leftarrow dp(v, k, 1) + hp(v)$$
上面这个是 $u, v$ 都活着, 需要多余的代价是 $hp(v)$.
注意01背包滚动数组转移方式, 写成代码长这样:
|
|
注意, 不要写成这样:
|
|
看这句转移:
|
|
这个东西乍一看好像没啥问题, 但是, 如果 x
或者 k
可以取0, 那么就会有 “自己转移自己” 的情况. 这显然是不合理的. 然而, 树形背包每一个点, 都是一个单独的 01 背包. 这个方程是拿滚动数组滚掉了一维. “这个点从上一层的这个点转移过来” 才是合法并且必要的转移. 举个例子, 假如 $x = k = 0$, 那么有 $dp(u, 0) \leftarrow dp(u, 0) + dp(v, 0)$. 这个方程实际上是, $dp(u, i, 0) \leftarrow dp(u, i-1, 0) + dp(v, s, 0)$, ($s$ 为 $v$ 的儿子个数).
所以, 我们只要想那种不完全滚掉的背包一样, 保留两行, 就可以正确转移了. 由于树形 dp 不好保留上一行, 可以新开一个 tmp
数组, 由当前行转移, 做完以后给他复制回去就行.
下面是正确的板子:
|
|