【题解】BZOJ 4557 [JLoi2016]侦察守卫

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(j0) 这个状态可以通过在 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=vson(u)min{fu,j+fv,j,fv,j+1+fu,(j+1)}vson(u)fv,(j1)
第一个式子表示两种决策,一种是能向上覆盖 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 j0 的情况用 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=vson(u)min{fu,j+gv,j,fv,j+1+gu,j+1}vson(v)gv,j1

C o d e Code Code

#include 
#include 
#include 
#include 
#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;
}


你可能感兴趣的:(树形DP)