bzoj4557: [JLoi2016]侦察守卫

bzoj4557: [JLoi2016]侦察守卫
题意:给出一棵n(<=5e5)个点的树,可以选一些点放置守卫,覆盖与其距离不超过d(<=20)的所有点。每个点放置守卫有一定代价。给出m(<=n)个指定点,求所有指定点被覆盖的最小代价。


题解

我们感性地跑这样一个dp:
· dp[i][d]表示以i为根的子树中,指定点被完全覆盖的最小代价。
· dp[i][d-j]表示子树中指定点被完全覆盖,同时能够覆盖i向上j层的最小代价。
· dp[i][d+j]表示i向下j层已经被覆盖后,剩下指定点被完全覆盖的最小代价。
怎么描述得这么乱呢
然后我们随便写一写转移就好啦!

复杂度O(nd)。


代码
#include<cstdio>
#define N 500001
#define inf 1e9
#define D 41
using namespace std;
int n,m,d,p,sum,mn,f[N],c[N][D];
bool ned[N];
int to[N<<1],hd[N<<1],lk[N],cnt;
void add(int u,int v)
{to[++cnt]=v,hd[cnt]=lk[u],lk[u]=cnt;}
char ch;
void read(int &x)
{
    x=0;
    while(ch<'0'|ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')
    x=x*10+ch-'0',ch=getchar();
}
void dfs(int x)
{
    int v,i,j;
    for(i=lk[x];i;i=hd[i])
    if(f[x]!=to[i])
    f[v=to[i]]=x,dfs(v),c[x][0]+=c[v][d<<1];
    for(j=1;j<=(d<<1);j++)
    {
        c[x][j]=c[x][j-1],mn=inf,sum=0;
        for(i=lk[x];i;i=hd[i])
        if(to[i]!=f[x])
        {
            v=to[i];
            if(j>d)p=c[v][j-1];
            else
            {
                p=c[v][(d<<1)-j];
                if(mn>c[v][j-1]-p)mn=c[v][j-1]-p;
            }
            sum+=p;
        }
        if(c[x][j]>sum+mn)c[x][j]=sum+mn;
        if(c[x][j]>sum&&(j>d||j==d&&!ned[x]))c[x][j]=sum;
    }
}
int main()
{
    read(n),read(d);
    for(int i=1;i<=n;i++)
    read(c[i][0]);
    read(m);
    while(m--)
    read(p),ned[p]=1;
    for(int i=1;i<n;i++)
    read(m),read(p),
    add(m,p),add(p,m);
    dfs(f[1]=1);printf("%d",c[1][d]);
}

题目无关

这道题曾为我带来深重的心理阴影。
插一段本来应属于【jloi2016游记】的东西
在下第一次 在比赛的某一天爆零 是JLOI2016的day1。多么令人怀念(大雾
当时感觉这道题好不可做啊,暴力调了1.5h没调出来,然后弃疗了。
后来查了题解,题解表示【这个dp很好理解】
但是当时在下非常弱,看不懂转移,因此受到了打击。

今天莫名想起来这道题,写了写,1A。(这只是因为以前见过题解…大约

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值