DescriptionDescriptionDescription
给定一个 nnn 个结点的二叉树 TTT ,并给出 mmm 个需要覆盖的点,每个结点可以花费 wiw_iwi 去覆盖以它为中心距离不大于 ddd 的所有结点,求最小花费。
SolutionSolutionSolution
树形 DP
我个人觉得这道题最难的部分是定状态。当 d=0d = 0d=0 的时候就是树的最大独立集问题,我们当时用 fi,0/1f_{i,0/1}fi,0/1 表示在 iii 子树中(取不取 iii 由 0/10/10/1 决定)最大点数。
但这道题 ddd 不等于 000 ,我们要定义 fi,jf_{i,j}fi,j 表示在子树 iii 被完全覆盖中,并向上覆盖 jjj 层的最小代价(向上只是一个方向,jjj 允许 为负数,此时表示子树 iii 中向下还有 jjj 层没有被覆盖)。
先讲初始化,fu,0=wuf_{u, 0} = w_ufu,0=wu 当且仅当 uuu 需要被覆盖。对 i∈[1,d]i \in [1, d]i∈[1,d] ,有 fu,i=wuf_{u, i} = w_ufu,i=wu 。且 fu,d+1=∞f_{u, d + 1} = \inftyfu,d+1=∞ 。不用过多解释吧。
我们注意到 fi,−j(j≥0)f_{i,-j} (j \geq 0)fi,−j(j≥0) 这个状态可以通过在 iii 点放置守卫从而转移到 fi,jf_{i,j}fi,j 。
于是,对 uuu 及它的子树 vvv,我们有 :
fu,j=minv∈son(u){fu,j+fv,−j,fv,j+1+fu,−(j+1)}fu,−j=∑v∈son(u)fv,−(j−1)
\begin{aligned}
f_{u,j} = & \min_{v \in son(u)}\{ f_{u,j} + f_{v, -j}, f_{v, j + 1} + f_{u, -(j+1)}\} \\
f_{u,-j} =& \sum_{v \in son(u)} f_{v, -(j- 1)}
\end{aligned}
fu,j=fu,−j=v∈son(u)min{fu,j+fv,−j,fv,j+1+fu,−(j+1)}v∈son(u)∑fv,−(j−1)
第一个式子表示两种决策,一种是能向上覆盖 j+1j + 1j+1 层的结点 ttt (为了达到 fu,jf_{u,j}fu,j)不在当前处理的范围内,另一种是 zzz 恰好就是 vvv 。这个比较难以理解,需要自己多画图加以理解。
第二个式子比较好理解,每个 vvv 都不放守卫,就直接累加就可以了。
另外对 j>0j > 0j>0 的情况需要做后缀最小值,对 j<0j < 0j<0 的情况需要做前缀最小值。这很好理解,我们拿 j>kj>kj>k 的情况简单说明一下,假如有 fi,j<fi,kf_{i,j} < f_{i,k}fi,j<fi,k ,这就说明状态 fi,kf_{i,k}fi,k 覆盖层数少还花费高,在 fi,jf_{i,j}fi,j 面前就只能淘汰了。
程序具体实现的时候由于 C++C++C++ 不支持负下标而且为了方便,我们将 j≤0j \leq 0j≤0 的情况用 gi,jg_{i,j}gi,j 存储(具体意义在前文已经有所提及)。据此重写的状态转移方程如下:
fu,j=minv∈son(u){fu,j+gv,j,fv,j+1+gu,j+1}gu,j=∑v∈son(v)gv,j−1
\begin{aligned}
f_{u,j} =& \min_{v \in son(u)} \{ f_{u,j} + g_{v, j}, f_{v, j + 1} + g_{u, j + 1} \} \\
g_{u,j} = & \sum_{v \in son(v)} g_{v, j - 1}
\end{aligned}
fu,j=gu,j=v∈son(u)min{fu,j+gv,j,fv,j+1+gu,j+1}v∈son(v)∑gv,j−1
CodeCodeCode
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 500005
#define D 21
#define infty 1e9
struct edgeType {
int to, next;
} edge[N << 1];
int head[N];
inline void addEdge(int from, int to) {
static int cnt = 0;
edge[++cnt] = (edgeType){to, head[from]};
head[from] = cnt;
}
bool cover[N];
int n, d, m;
int w[N], f[N][D], g[N][D];
inline void solve(int u, int p) {
if (cover[u] == true) f[u][0] = g[u][0] = w[u];
for (int i= 1; i <= d; i++) f[u][i] = w[u]; f[u][d + 1] = infty;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == p) continue;
solve(v, u);
for (int j = d; j >= 0; j--) {
f[u][j] = std::min(f[u][j] + g[v][j], g[u][j + 1] + f[v][j + 1]);
f[u][j] = std::min(f[u][j], f[u][j + 1]);
}
g[u][0] = f[u][0];
for (int j = 1; j <= d + 1; j++) {
g[u][j] += g[v][j - 1];
g[u][j] = std::min(g[u][j], g[u][j - 1]);
}
}
}
int main() {
scanf("%d%d", &n, &d);
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
int x;
scanf("%d", &x);
cover[x] = true;
}
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
addEdge(u, v);
addEdge(v, u);
}
solve(1, 0);
printf("%d\n", f[1][0]);
return 0;
}