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;
}
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/8544819.html