BZOJ5072[Lydsy十月月赛] 小A的树 解题报告【树上背包/树形DP】

Problem Statement
小A 成为了一个园艺家!他有一棵n 个节点的树(如果你不知道树是什么,请看Hint 部分)。他不小心打翻了墨水瓶,使得树的一些节点被染黑了。小A 发现这棵染黑了的树很漂亮,于是想从树中取出一个x 个点的联通子图,使得这些点中恰有y 个黑点,他想知道他的愿望能否实现。可是他太小,不会算,请你帮帮他。
解题报告
这道题可以理解为:有n个点,每个点有其点权,一些点的点权是1,一些点的点权是0,选择每一个点都有代价,代价为1,问能否用大小为x的背包装下权值为y的点。
关于树上背包,有这么一篇博客,这里我们不仅算出最多能选的价值,也算出最小能选到的价值就好了。
具体的状态:

dp[u][j+k]//以u为根节点,一个子树选k个点,其他子树选u个点的最小价值
g[u][j+k]//以u为根节点,一个子树选k个点,其他子树选u个点的最大价值

转移:

dp[u][j+k]=min(dp[u][j+k],dp[u][j]+dp[v][k]),g[u][j+k]=max(g[u][j+k],g[u][j]+g[v][k]);

dp数组初值赋+inf,g数组赋0,搜索到每一个点的时候更改dp[u][1]/dp[v][1]。
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5000;
struct edge
{
    int v,next;
}ed[2*N+5];
int T,n,q;
int w[N+5],size[N+5];
int dp[N+5][N+5],g[N+5][N+5];
int vmax[N+5],vmin[N+5];
int head[N+5],num;
void build(int u,int v)
{
    ed[++num].v=v;
    ed[num].next=head[u];
    head[u]=num;
}
void dfs(int u,int f)
{
    size[u]=1;
    dp[u][1]=w[u],g[u][1]=w[u];
    for(int i=head[u];i!=-1;i=ed[i].next)
    {
        int v=ed[i].v;
        if(v==f)continue;
        dfs(v,u);
        for(int j=size[u];j;j--)
        for(int k=0;k<=size[v];k++)
        dp[u][j+k]=min(dp[u][j+k],dp[u][j]+dp[v][k]),
        g[u][j+k]=max(g[u][j+k],g[u][j]+g[v][k]);
        size[u]+=size[v];
    }
    for(int i=1;i<=n;i++)vmin[i]=min(vmin[i],dp[u][i]),vmax[i]=max(vmax[i],g[u][i]);
}
void init()
{
    memset(head,-1,sizeof(head));num=0;
    memset(dp,0x3f,sizeof(dp));
    memset(vmin,0x3f,sizeof(vmin));
    memset(vmax,0,sizeof(vmax));
    memset(g,0,sizeof(g));
}
int main()
{
    for(scanf("%d",&T);T;T--)
    {
        init();
        scanf("%d%d",&n,&q);
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            build(u,v);
            build(v,u);
        }
        for(int i=1;i<=n;i++)scanf("%d",&w[i]);
        dfs(1,0);
        for(int i=1;i<=q;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if(y>=vmin[x]&&y<=vmax[x])printf("YES\n");
            else printf("NO\n");
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值