tree 【NOIP2016提高A组五校联考2】

22 篇文章 0 订阅
8 篇文章 0 订阅

题目(直接copy了)

Description

给一棵n 个结点的有根树,结点由1 到n 标号,根结点的标号为1。每个结点上有一个物品,第i 个结点上的物品价值为vi。
你需要从所有结点中选出若干个结点,使得对于任意一个被选中的结点,其到根的路径上所有的点都被选中,并且选中结点的个数不能超过给定的上限lim。在此前提下,你需要最大化选中结点上物品的价值之和。
求这个最大的价值之和。

Input

第一行为两个整数n; lim
接下来n 行,第i 行包含一个整数vi,表示结点i 上物品的价值。
接下来n- 1 行,每行包含两个整数u; v, 描述一条连接u; v 结点的树边。

Output

输出一行答案。

Sample Input

6 4
-5
4
-6
6
9
6
3 2
3 1
2 4
2 5
1 6

Sample Output

2

Data Constraint

对于前20% 的数据,1<=n; lim<=10
对于前60% 的数据,1<=n; lim<=100
对于100% 的数据,1<=n; lim<=3000; |vi| <=10^5 数据有梯度,保证给出的是合法的树。


剖解题目

。。。。


思路

一眼就是树形dp嘛。。。。然而一时间的脑子卡顿,没有想到这是背包。。。。。(毕竟我基本没打过背包)。


解法

60%:树形dp,有限背包。设 fi,j 表示以i为根节点的子树选了j个能获得的最大价值,然后就有

fi,j=max(fi,min(size[i],lim)+fson,min(i1,size[son]))

size[x]表示以x为节点的子树的大小。
100%:上述方法可以水过,实际复杂度没有那么高。


代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)

using namespace std;

const int maxn=3005;
int n,lim,ans,num;
int next[2*maxn],go[2*maxn],fre[maxn],a[maxn],f[maxn][maxn],size[maxn];

void add(int x,int y)
{
    go[++num]=y;
    next[num]=fre[x];
    fre[x]=num;
}
void dfs(int x,int dad)
{
    f[x][0]=0; f[x][1]=a[x];
    int u=fre[x];
    while (u){
        bool bo=false;
        if (go[u]!=dad) {
            dfs(go[u],x);
            size[x]+=size[go[u]];
            bo=true; 
        }
        if (bo) {
            for(int i=min(size[x],lim);i>=1;i--){
                fo(j,0,min(i-1,size[go[u]]))
                if (i-j>=0)
                    f[x][i]=max(f[x][i],f[x][i-j]+f[go[u]][j]);
            }

        }
        u=next[u];
    }
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d%d",&n,&lim);
    fo(i,1,n) scanf("%d",&a[i]),size[i]=1;
    fo(i,1,n-1){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    memset(f,128,sizeof(f));
    dfs(1,0);
    fo(i,1,lim) ans=max(ans,f[1][i]);
    printf("%d",ans);
    fclose(stdin); fclose(stdout);
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值