bzoj千题计划272:bzoj4557: [JLoi2016]侦察守卫
http://www.lydsy.com/JudgeOnline/problem.php?id=4557
假设当前到了x的子树,现在是合并 x的第k个子树
f[x][j] 表示x的前k-1个子树该覆盖的完全覆盖,而且还能向上覆盖j层的最小代价
这个向上是针对x来说的,即可以向x的祖先方向再覆盖j层
对于第k个子树的意义就是,兄弟子树放置的守卫可以帮x的第k个子树覆盖前j层(第1层为x的子节点)
那么相应的就要有一个状态来表示这个 可以让兄弟子树 帮忙覆盖 的前j层
g[x][j] 表示还需要覆盖x的前k个子树中的前j层,且第j层以下该覆盖的完全覆盖(第1层为x)的最小代价
状态转移:
设x的第k个子节点为y
向x的上方覆盖j层,只需要x的子节点中有一个子节点z能向上覆盖j+1层 即可
所以f的转移有两种:z是前k-1个子节点中的,z是第k个子节点
f[x][j]=min(f[x][j]+g[y][j] ,f[y][j+1]+g[x][j+1])
g[x][j]+=g[y][j-1]
但是有可能x 再向上恰好覆盖j层的代价要小于再向上恰好覆盖j-1层的代价
即覆盖的更多代价反而要小
所以将f的状态定义改为 向上覆盖至少j层的最小代价
同理,g的状态定义改为还需要覆盖至多j层的最小代价
对f[x][]做一个后缀最小值,g[x][]做一个前缀最小值
#include<cstdio> #include<iostream> using namespace std; #define N 500001 int d; int w[N]; bool use[N]; int front[N],to[N<<1],nxt[N<<1],tot; int f[N][22],g[N][22]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } void add(int u,int v) { to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; to[++tot]=u; nxt[tot]=front[v]; front[v]=tot; } void dfs(int x,int fa) { for(int i=1;i<=d;++i) f[x][i]=w[x]; if(use[x]) g[x][0]=f[x][0]=w[x]; f[x][d+1]=1e9; int t; for(int i=front[x];i;i=nxt[i]) { t=to[i]; if(t!=fa) { dfs(t,x); for(int j=0;j<=d;++j) f[x][j]=min(f[x][j]+g[t][j],f[t][j+1]+g[x][j+1]); for(int j=d;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[t][j-1]; for(int j=1;j<=d;++j) g[x][j]=min(g[x][j],g[x][j-1]); } } } int main() { int n,m,x; read(n); read(d); for(int i=1;i<=n;++i) read(w[i]); read(m); while(m--) { read(x); use[x]=true; } int u,v; for(int i=1;i<n;++i) { read(u); read(v); add(u,v); } dfs(1,0); printf("%d",g[1][0]); return 0; }