【树规】 JZOJ4814

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 

-6 



3 2 
3 1 
2 4 
2 5 
1 6 
Sample Output 

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

The Solution

看到这道题第一眼想到的就是数规。
很容易想到当计算好儿子节点的状态之后,需要枚举儿子节点以及兄弟节点状态的时间问lim*lim 所以很明显超时。
改变DP状态的意义。F[i][j]表示 以i为根节点,选择j个节点所得到的最大值。当中 j个点必须是在当前已经dfs到的结点的集合中。
那么,考虑将先把父亲的值传递给儿子 ,再dfs儿子节点。之后把儿子节点dfs之后的值传递给父亲。
仔细想一想。这样可以让父亲节点包含所有已经DFS到的儿子节点选或者不选的情况。所以DFS下一个儿子的时候一定包含了DFS上一个儿子以及之前所有儿子的情况。
姑且当做是一个做题技巧记下来吧。
具体的操作:
for (int i=0;i<=m;i++)
                f[v][i]=f[s][i]+value[v];
            dfs(v);
            for (int i=m;i>=1;i--)
                f[s][i]=max(f[s][i],f[v][i-1]);

代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int value[10001],head[10001],num_edge,a,b,m,n;
int vis[10001];
int f[3001][100001];
struct Edge{
    int from,to,next;
}edge[10001];
void add(int f,int t)
{
    edge[++num_edge].next=head[f];
    edge[num_edge].to=t;
    head[f]=num_edge;
}
void dfs(int s)
{
    for (int i=head[s];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(!vis[v])
        {
            vis[v]=1;
            for (int i=0;i<=m;i++)
                f[v][i]=f[s][i]+value[v];
            dfs(v);
            for (int i=m;i>=1;i--)
                f[s][i]=max(f[s][i],f[v][i-1]);
        }
         
    }
}
int main()
{
    //freopen("a.in","r",stdin);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    cin>>value[i];
    for (int i=1;i<=n-1;i++){
        cin>>a>>b;
        add(a,b);add(b,a);
    }vis[0]=1;
    add(0,1);add(1,0);
    dfs(0);
    cout<<f[0][m];
    return 0;
     
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值