JLOI/SHOI 2016 侦察守卫 题解

题目传送门

题目大意: 给出一棵树,有 m m m 个关键点,在一个点放置侦查守卫可以监视距离为 d d d 以内的点,在每个点放侦查守卫有不同代价,求最小代价监视所有关键点。

题解

不难想到树形 d p dp dp,但这题状态设计十分巧妙,设 f x , i f_{x,i} fx,i 表示 x x x 子树内关键点都被监视了,且还能监视到 x x x 距离小于等于 i i i 的点最小代价, g x , i g_{x,i} gx,i 表示 x x x 子树内到 x x x 距离小于 i i i 的都没被监视,但大于等于 i i i 的都被监视了的最小代价。

定义上需要注意的是, f x , 0 f_{x,0} fx,0 只能监视子树,不能再往外监视,而 g x , 0 g_{x,0} gx,0 表示子树内都被监视,因为不存在到 x x x 距离小于 0 0 0 的点,也就是要注意小于等于小于的问题。

考虑 x x x 的一棵子树 y y y x x x 的贡献,先枚举出 i i i,考虑 f x , i f_{x,i} fx,i,假如 x x x 要能往外监视 i i i 层,那么必定有一个子树能往外监视 i + 1 i+1 i+1 层,这个子树可以是之前的子树,那么贡献就是 f x , i + g y , i f_{x,i}+g_{y,i} fx,i+gy,i,因为 y y y 往下 i − 1 i-1 i1 层也可以顺便被 x x x 监视;这个子树也可以是 y y y,贡献就是 f y , i + 1 + g x , i + 1 f_{y,i+1}+g_{x,i+1} fy,i+1+gx,i+1

再考虑 g g g 的转移,显然有 g x , 0 = f x , 0 g_{x,0}=f_{x,0} gx,0=fx,0,而对于 g x , i g_{x,i} gx,i,加上子树的 g y , i − 1 g_{y,i-1} gy,i1 即可。

但是有一个小问题,有可能 f x , i + 1 < f x , i f_{x,i+1}<f_{x,i} fx,i+1<fx,i,即监视更多层的代价会更小,由于 f f f 的定义是恰好监视 i i i 层所以可能出现这样的情况。

不妨稍改一下定义:令 f x , i f_{x,i} fx,i 表示 x x x 往外至少能监视 i i i 层, g x , i g_{x,i} gx,i 表示 x x x 往下至多有 i − 1 i-1 i1 层未被监视,那么对 g , f g,f g,f 再分别取一个前后缀 min ⁡ \min min 即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 500010

int n,d,m,a[maxn];
struct edge{
    
    int y,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y){
    
    e[++len]=(edge){
    
    y,first[x]};first[x]=len;}
bool key[maxn];
int f[maxn][20],g[maxn][20];
void dfs(int x,int fa){
    
    
	for(int i=1;i<=d;i++)f[x][i]=a[x];
	if(key[x])f[x][0]=g[x][0]=a[x];
	f[x][d+1]=1e9;
	for(int i=first[x];i;i=e[i].next){
    
    
		int y=e[i].y;if(y==fa)continue;
		dfs(y,x);
		for(int j=0;j<=d;j++)f[x][j]=min(f[x][j]+g[y][j],f[y][j+1]+g[x][j+1]);
		for(int j=d-1;j>=0;j--)f[x][j]=min(f[x][j],f[x][j+1]);
		g[x][0]=f[x][0];
		for(int j=1;j<=d;j++)g[x][j]+=g[y][j-1];
		for(int j=1;j<=d;j++)g[x][j]=min(g[x][j],g[x][j-1]);
	}
}

int main()
{
    
    
	scanf("%d %d",&n,&d);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	scanf("%d",&m);
	for(int i=1,x;i<=m;i++)scanf("%d",&x),key[x]=true;
	for(int i=1,x,y;i<n;i++){
    
    
		scanf("%d %d",&x,&y);
		buildroad(x,y);buildroad(y,x);
	}
	dfs(1,0);
	printf("%d",g[1][0]);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109112786