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

本文介绍了一种使用树形动态规划方法解决特定覆盖问题的算法。问题要求在一个给定的二叉树中,选择一些节点进行覆盖,使得指定的节点都被覆盖到,同时花费的总费用最小。通过定义合适的状态并进行状态转移,最终得到最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DescriptionDescriptionDescription

给定一个 nnn 个结点的二叉树 TTT ,并给出 mmm 个需要覆盖的点,每个结点可以花费 wiw_iwi 去覆盖以它为中心距离不大于 ddd 的所有结点,求最小花费。

SolutionSolutionSolution

树形 DP

我个人觉得这道题最难的部分是定状态。当 d=0d = 0d=0 的时候就是树的最大独立集问题,我们当时用 fi,0/1f_{i,0/1}fi,0/1 表示在 iii 子树中(取不取 iii0/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(j0) 这个状态可以通过在 iii 点放置守卫从而转移到 fi,jf_{i,j}fi,j

于是,对 uuu 及它的子树 vvv,我们有 :
fu,j=min⁡v∈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=vson(u)min{fu,j+fv,j,fv,j+1+fu,(j+1)}vson(u)fv,(j1)
第一个式子表示两种决策,一种是能向上覆盖 j+1j + 1j+1 层的结点 ttt (为了达到 fu,jf_{u,j}fu,j)不在当前处理的范围内,另一种是 zzz 恰好就是 vvv 。这个比较难以理解,需要自己多画图加以理解。

第二个式子比较好理解,每个 vvv 都不放守卫,就直接累加就可以了。

另外对 j&gt;0j &gt; 0j>0 的情况需要做后缀最小值,对 j&lt;0j &lt; 0j<0 的情况需要做前缀最小值。这很好理解,我们拿 j&gt;kj&gt;kj>k 的情况简单说明一下,假如有 fi,j&lt;fi,kf_{i,j} &lt; 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 0j0 的情况用 gi,jg_{i,j}gi,j 存储(具体意义在前文已经有所提及)。据此重写的状态转移方程如下:
fu,j=min⁡v∈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} =&amp; \min_{v \in son(u)} \{ f_{u,j} + g_{v, j}, f_{v, j + 1} + g_{u, j + 1} \} \\ g_{u,j} = &amp; \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

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


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值