【JLOI2016/SHOI2016】侦察守卫(树形DP)

考虑树形 DP,假设我们已经考虑完当前子树内监听点的放置情况,根为 u u u,考虑我们要记录什么状态: u u u 子树内的监听点向子树外还能监听多远, u u u 子树内距离根最远的未被监听点有多远。

发现当第二个状态存在时,第一个状态是无用的,因为若 u u u 子树内存在一个未被监听的点 v v v,设其到 u u u 的距离为 d d d,那么:

  • u u u 子树内的监听点最远向子树外的监听距离不会超过 d d d,否则 v v v 就能被监听。
  • v v v 最后肯定被 u u u 子树外的某个点 x x x 监听,那么所有距离 u u u 小于等于 d d d 的点都能被 x x x 监听到。

也就是说当 v v v 存在时,我们无需考虑 u u u 子树内的监听点向 u u u 子树外的监听情况,第一个状态是无用的。

那么这样就能简化 DP 状态:我们设 f u , i f_{u,i} fu,i 表示考虑完 u u u 子树内监听点的放置状况, u u u 子树内离 u u u 最远的未被监听的点的距离为 i i i,所需的最小代价。设 g u , i g_{u,i} gu,i 表示考虑完 u u u 子树内监听点的放置状况, u u u 子树内已经不存在未被监听的点,且 u u u 子树内的监听点向子树外最远能监听距离 i i i,所需的最小代价。

转移时合并 u u u 当前已经考虑的子树和枚举的儿子 v v v 的子树,分 f u , f v f_u,f_v fu,fv f u , g v f_u,g_v fu,gv g u , f v g_u,f_v gu,fv g u , g v g_u,g_v gu,gv 四种情况合并:
f u , i + f v , j → f u , max ⁡ ( i , j + 1 ) f u , i + g v , j → { g u , j − 1 if  j − 1 ≥ i f u , i if  j − 1 < i g u , i + f v , j → { g u , i if  i ≥ j + 1 f u , j + 1 if  i < j + 1 g u , i + g v , j → g u , max ⁡ ( i , j − 1 ) \begin{aligned} f_{u,i}+f_{v,j}&\to f_{u,\max(i,j+1)}\\ f_{u,i}+g_{v,j}&\to \begin{cases}g_{u,j-1}&\text{if }j-1\geq i\\f_{u,i}&\text{if }j-1<i\end{cases}\\ g_{u,i}+f_{v,j}&\to \begin{cases}g_{u,i}&\text{if }i\geq j+1\\f_{u,j+1}&\text{if }i<j+1\end{cases}\\ g_{u,i}+g_{v,j}&\to g_{u,\max(i,j-1)} \end{aligned} fu,i+fv,jfu,i+gv,jgu,i+fv,jgu,i+gv,jfu,max(i,j+1){gu,j1fu,iif j1iif j1<i{gu,ifu,j+1if ij+1if i<j+1gu,max(i,j1)
使用前缀和/后缀和优化即可做到 O ( n D ) O(nD) O(nD)

初始时每个点都各自形成单独一棵子树,对于所有点设 g u , D ← w u g_{u,D}\gets w_u gu,Dwu,对需要被监听的点设 f u , 0 = 0 f_{u,0}=0 fu,0=0,对不需要被监听的点设 g u , 0 = 0 g_{u,0}=0 gu,0=0

#include<bits/stdc++.h>

#define D 25
#define N 500010
#define INF 0x7fffffff

using namespace std;

inline void upmin(int &x,int y){if(y<x) x=y;}

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,d,w[N];
int cnt,head[N],nxt[N<<1],to[N<<1];
int f[N][D],g[N][D],pref[N][D],preg[N][D];

void adde(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

inline void premin(int *f,int *pre)
{
	for(int i=0;i<=d;i++)
		pre[i]=min(i?pre[i-1]:INF,f[i]);
}

void dfs(int u,int fa)
{
	static int ff[D],gg[D];
	premin(f[u],pref[u]),premin(g[u],preg[u]);
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa) continue;
		dfs(v,u);
		memset(ff,0x3f,sizeof(ff));
		memset(gg,0x3f,sizeof(gg));
		for(int i=1;i<=d;i++)
			upmin(ff[i],f[u][i]+pref[v][i-1]);
		for(int i=0;i<d;i++)
			upmin(ff[i+1],pref[u][i+1]+f[v][i]);
		for(int i=1;i<=d;i++)
			upmin(gg[i-1],pref[u][i-1]+g[v][i]);
		for(int i=0;i<=d;i++)
			upmin(ff[i],f[u][i]+preg[v][i]);
		for(int i=1;i<=d;i++)
			upmin(gg[i],g[u][i]+pref[v][i-1]);
		for(int i=0;i<d;i++)
			upmin(ff[i+1],preg[u][i]+f[v][i]);
		for(int i=0;i<=d;i++)
			upmin(gg[i],g[u][i]+preg[v][min(i+1,d)]);
		for(int i=1;i<=d;i++)
			upmin(gg[i-1],preg[u][i-1]+g[v][i]);
		memcpy(f[u],ff,sizeof(f[u]));
		memcpy(g[u],gg,sizeof(g[u]));
		premin(f[u],pref[u]),premin(g[u],preg[u]);
	}
}

int main()
{
	n=read(),d=read();
	memset(f,0x3f,sizeof(f));
	memset(g,0x3f,sizeof(g));
	for(int i=1;i<=n;i++) g[i][d]=read();
	for(int i=1,m=read();i<=m;i++) f[read()][0]=0;
	for(int i=1;i<=n;i++) if(f[i][0]) g[i][0]=0;
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		adde(u,v),adde(v,u);
	}
	dfs(1,0);
	int ans=INF;
	for(int i=0;i<=d;i++) ans=min(ans,g[1][i]);
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值