您的位置:首页 > 其它

bzoj千题计划272:bzoj4557: [JLoi2016]侦察守卫

2018-03-11 17:15 309 查看
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
;
bool use
;

int front
,to[N<<1],nxt[N<<1],tot;

int f
[22],g
[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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: