树形 dp,经典模型(《侦察守卫模型》大雾)

洛谷题面传送门

经典题一道,下次就称这种”覆盖距离不超过 xxx 的树形 dp“为《侦察守卫模型》

我们考虑树形 \(dp\),设 \(f_{x,j}\) 表示钦定了 \(x\) 子树内的点选/不选的状态,且 \(x\) 子树内必须要被覆盖的点都被覆盖,\(x\)\(1\sim j\) 级祖先都被覆盖了的最小代价,再设 \(g_{x,j}\) 表示 \(x\) 子树内距离 \(x\ge j\) 的必须要被覆盖的点都被覆盖,而 \(x\) 子树内距离 \(x\) \(<j\) 的点及 \(x\) 的祖先的覆盖状态不做要求的最小代价。根据 \(f,g\) 的定义容易发现:

  • \(\forall i,j,g_{x,i}\le f_{x,j}\),(证明?显然!)
  • \(g_{x,0}=f_{x,0}\)(证明?显然!)

接下来考虑如何转移,假设我们往 \(u\) 子树内加入一个 \(v\) 子树,那么有:

  • \(f_{u,i}=\min(f_{u,i}+g_{v,i},g_{u,i+1}+f_{v,i+1})\)
  • \(g_{u,i}=g_{u,i}+g_{v,i-1}\)
  • \(g_{u,0}=f_{u,0}\)

稍微解释一下上面三个式子,首先第三个式子根据 \(dp\) 数组的定义即可明白。\(f\) 的转移中,\(\min\) 里面第一个表示 \(u\) 本来就可以向上覆盖的情况,由于 \(u\) 可以向上覆盖 \(i\) 的距离,自然也可以向下覆盖 \(i\) 的距离,此时只要 \(v\) 子树内距离 \(v\) \(\ge i\) 的点都被覆盖了就符合要求,故代价为 \(f_{u,i}+g_{v,i}\),后面的 \(g_{u,i+1}+f_{v,i+1}\) 也是同样的道理,只不过这里 \(u\) 要向上覆盖 \(i\)\(v\) 就必须要向上覆盖 \(i+1\)。有人可能会问,两个 \(f\) 加在一起的转移到哪里去了?被你吃了?不难发现,根据 \(\forall i,j,g_{x,i}\le f_{x,j}\),两个 \(f\) 的转移肯定不如一 \(f\)\(g\) 来得优,因此我们肯定不会两个 \(f\) 加在一起。而 \(g\) 的转移就相对来说比较容易了,只要 \(v\) 子树内距离 \(v\) \(\ge i\) 的点都被覆盖了就符合”\(u\) 子树内距离 \(u\) \(\ge i\) 的点都被覆盖“的要求。

同时,根据 \(f,g\) 的定义,还有:

  • \(f_{u,i}=\min(f_{u,i},f_{u,i-1})\)
  • \(g_{u,i}=\min(g_{u,i},g_{u,i+1})\)

这样我们就成功地处理了转移有关的问题。

最后就是 DFS 到某个点时 DP 数组的初始值,首先 \(f_{x,i}=w_x(i\in[1,D])\),因为当你只有一个点时,只有放置侦察守卫才能向上覆盖,而如果 \(x\) 是 B 神可能出现的位置,那么有 \(f_{x,0}=g_{x,0}=w_x\),否则 \(f_{x,0}=g_{x,0}=0\),因为如果 B 神不可能出现在 \(x\),那么 \(x\) 不一定要被覆盖。

时间复杂度 \(\Theta(nD)\)

貌似这题和 CF1517F 很像?要是我早半年做到这道题说不定我那场就能一下涨上 2500 了(bushi

const int MAXN=5e5;
const int MAXD=20;
const int INF=0x3f3f3f3f;
int n,d,w[MAXN+5],hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0,vis[MAXN+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int f[MAXN+5][MAXD+2],g[MAXN+5][MAXD+2];
void dfs(int x,int fa){
	for(int i=1;i<=d;i++) f[x][i]=w[x];f[x][d+1]=INF;
	if(vis[x]) f[x][0]=g[x][0]=w[x];
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==fa) continue;dfs(y,x);
		for(int i=d;~i;i--) f[x][i]=min(f[x][i]+g[y][i],g[x][i+1]+f[y][i+1]);
		for(int i=d;~i;i--) f[x][i]=min(f[x][i],f[x][i+1]);
		g[x][0]=f[x][0];
		for(int i=1;i<=d;i++) g[x][i]+=g[y][i-1];
		for(int i=1;i<=d;i++) chkmin(g[x][i],g[x][i-1]);
	}
}
int main(){
	scanf("%d%d",&n,&d);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	int m;scanf("%d",&m);while(m--){int x;scanf("%d",&x);vis[x]=1;}
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	dfs(1,0);printf("%d\n",f[1][0]);
	return 0;
}