D e s c r i p t i o n Description Description
给定一个 n n n 个结点的二叉树 T T T ,并给出 m m m 个需要覆盖的点,每个结点可以花费 w i w_i wi 去覆盖以它为中心距离不大于 d d d 的所有结点,求最小花费。
S o l u t i o n Solution Solution
树形 DP
我个人觉得这道题最难的部分是定状态。当 d = 0 d = 0 d=0 的时候就是树的最大独立集问题,我们当时用 f i , 0 / 1 f_{i,0/1} fi,0/1 表示在 i i i 子树中(取不取 i i i 由 0 / 1 0/1 0/1 决定)最大点数。
但这道题 d d d 不等于 0 0 0 ,我们要定义 f i , j f_{i,j} fi,j 表示在子树 i i i 被完全覆盖中,并向上覆盖 j j j 层的最小代价(向上只是一个方向, j j j 允许 为负数,此时表示子树 i i i 中向下还有 j j j 层没有被覆盖)。
先讲初始化, f u , 0 = w u f_{u, 0} = w_u fu,0=wu 当且仅当 u u u 需要被覆盖。对 i ∈ [ 1 , d ] i \in [1, d] i∈[1,d] ,有 f u , i = w u f_{u, i} = w_u fu,i=wu 。且 f u , d + 1 = ∞ f_{u, d + 1} = \infty fu,d+1=∞ 。不用过多解释吧。
我们注意到 f i , − j ( j ≥ 0 ) f_{i,-j} (j \geq 0) fi,−j(j≥0) 这个状态可以通过在 i i i 点放置守卫从而转移到 f i , j f_{i,j} fi,j 。
于是,对
u
u
u 及它的子树
v
v
v,我们有 :
f
u
,
j
=
min
v
∈
s
o
n
(
u
)
{
f
u
,
j
+
f
v
,
−
j
,
f
v
,
j
+
1
+
f
u
,
−
(
j
+
1
)
}
f
u
,
−
j
=
∑
v
∈
s
o
n
(
u
)
f
v
,
−
(
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
+
1
j + 1
j+1 层的结点
t
t
t (为了达到
f
u
,
j
f_{u,j}
fu,j)不在当前处理的范围内,另一种是
z
z
z 恰好就是
v
v
v 。这个比较难以理解,需要自己多画图加以理解。
第二个式子比较好理解,每个 v v v 都不放守卫,就直接累加就可以了。
另外对 j > 0 j > 0 j>0 的情况需要做后缀最小值,对 j < 0 j < 0 j<0 的情况需要做前缀最小值。这很好理解,我们拿 j > k j>k j>k 的情况简单说明一下,假如有 f i , j < f i , k f_{i,j} < f_{i,k} fi,j<fi,k ,这就说明状态 f i , k f_{i,k} fi,k 覆盖层数少还花费高,在 f i , j f_{i,j} fi,j 面前就只能淘汰了。
程序具体实现的时候由于
C
+
+
C++
C++ 不支持负下标而且为了方便,我们将
j
≤
0
j \leq 0
j≤0 的情况用
g
i
,
j
g_{i,j}
gi,j 存储(具体意义在前文已经有所提及)。据此重写的状态转移方程如下:
f
u
,
j
=
min
v
∈
s
o
n
(
u
)
{
f
u
,
j
+
g
v
,
j
,
f
v
,
j
+
1
+
g
u
,
j
+
1
}
g
u
,
j
=
∑
v
∈
s
o
n
(
v
)
g
v
,
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
C o d e Code Code
#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;
}