【BZOJ 4557】【JLOI 2016】侦查守卫

1 篇文章 0 订阅

鉴于省选杂题题意实在过于复杂QAQ之后就直接上题解了
这题如果想不出来正解的话。。估计只能拿10分,d=1的20分不知道能不能乱搞一发。。后面的几个点毫无区分度啊

令:
f[i][j]表示i这个节点下面j层以下都被覆盖的代价
g[i][j]表示以i这个节点为根的子树、以及向上长度为j的一条链都被覆盖的代价

从下往上dp显然。这个g数组我一开始想到了,因为当前i这个点对上面的影响只有向上长度为d的一条链。但是这个f数组太鬼畜了,后来想一下,当前点所能影响的范围直径为d,那么就要记录d下面的节点。
首先假设当前点x要放守卫,那么如果不放守卫,所更新的范围就是f[x][0-d]和g[x][0-d]。f数组的更新是显然的,只要对一个一个加进来就行了;g数组的方法有点复杂,要在x的孩子中找出一个y,让y伸出一条链覆盖x上面[0-d]个节点,同时x下面的[0-d]个节点也会被覆盖,所以这个时候要加上一个f数组。
状态转移方程就不写了,程序里面还是比较清晰的。

#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 2100000000
#define mod 1000000007
#define N 600000
using namespace std;
int n,m,x,y,d,i;
int w[N],b[N],f[N][30],g[N][30];
vector<int> e[N];
void dfs(int x,int fa)
{
    int i,j,y;
    if (b[x] == 1) f[x][0] = g[x][0] = w[x];
    for (i = 1;i <= d; i++) g[x][i] = w[x];
    g[x][d+1] = inf;
    for (i = 0;i < e[x].size(); i++)
        if (e[x][i] != fa)
            {
                y = e[x][i];
                dfs(y,x);
                for (j = 0;j <= d; j++) g[x][j] = min(g[x][j]+f[y][j],g[y][j+1]+f[x][j+1]);
                for (j = d;j >= 0; j--) g[x][j] = min(g[x][j],g[x][j+1]);
                f[x][0] = g[x][0];
                for (j = 1;j <= d + 1; j++) f[x][j] += f[y][j-1];
                for (j = 1;j <= d + 1; j++) f[x][j] = min(f[x][j],f[x][j-1]);

            }
}
int main()
{
    scanf("%d%d",&n,&d);
    for (i = 1;i <= n; i++) scanf("%d",&w[i]);
    scanf("%d",&m);
    for (i = 1;i <= m; i++) {scanf("%d",&x); b[x] = 1;}
    for (i = 1;i < n; i++)
        {
            scanf("%d%d",&x,&y);
            e[x].push_back(y); e[y].push_back(x);
        }
    dfs(1,0);
    printf("%d",f[1][0]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值