【NOIP2017提高A组集训10.25】摘Galo (树形dp)

Description

0v0在野外看到了一棵Galo树,看到食物的0v0瞪大了眼睛,变成了OvO。
这棵Galo树可以看做是一棵以1号点为根的n个点的有根数,除了根节点以外,每个节点i都有一个Galo,美味度为w[i]。
OvO发现,如果她摘下了i号Galo,那么i的子树中的Galo以及i到根的路径上的其他Galo都会死掉。
OvO的袋子只能装k个Galo,她的嘴巴里还能叼1个,请问她所摘Galo的美味度之和的最大值是多少?

Input

第一行两个正整数n,k。
第二行到第n行,第i行两个正整数f[i],w[i],表示i号点的父亲为f[i] (保证x[i]

Output

一行一个非负整数,为最大美味值。

Sample Input

4 1
1 10
2 3
2 6

Sample Output

10

Data Constraint

30% n,k<=200
30% n*k*k<=10^7
40% n*k<=10^7
对于所有数据,n,k,w[i]<=10^5

Hint

尽管OvO最多可以摘两个Galo,但是最优情况是只摘下第二个点的Galo,美味度为10。


题解

这题朴素的dp复杂度是 O(nk2) ,做法就是选课
其实可以做到 O(nk) ,做法也不难,代码比朴素还短,只需要搞出来dfs序,然后在dfs序上dp就好了
为什么是 O(nk) 呢?
因为我们只需要遍历一遍状态,然后每一个状态由于是在dfs序上,所以只有两种转移:
1.选择它本身,并跳过它的子树,也就是在dfs序上加上它的子树大小
2.不选它本身,进入它的子树,也就是在dfs序上加1
然后就少了好多冗余状态。
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<vector>
#define ll long long
using namespace std;
inline int read(){
    int x=0;char ch=' ';int f=1;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')f=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*f;
}
struct edge{
    int to,next;
}e[200001];
int n,k,tot;
int head[100001];
inline void addedge(int x,int y){
    e[++tot].to=y;e[tot].next=head[x];head[x]=tot;
}
vector<ll> f[100005];
ll w[100005];
int size[100005];
int dfn[100005],dfn_clock;
inline void dfs(int x,int fa){
    size[x]=1;
    dfn[++dfn_clock]=x;
    for(int i=head[x];i;i=e[i].next){
        int u=e[i].to;
        if(u==fa)continue;
        dfs(u,x);
        size[x]+=size[u];
    }
}
inline ll dp(int i,int j){
    if(i>n)return 0;
    if(j==0)return 0;
    if(f[i][j])return f[i][j];
    f[i][j]=max(f[i][j],dp(i+size[dfn[i]],j-1)+w[dfn[i]]);
    f[i][j]=max(f[i][j],dp(i+1,j));
    return f[i][j];
}
int main(){
    freopen("galo.in","r",stdin);
    freopen("galo.out","w",stdout);
    n=read();k=read()+1;
    for(int i=2;i<=n;i++){
        int fa=read();
        w[i]=read();
        addedge(fa,i);addedge(i,fa);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++){
        f[i].resize(k+1);
    }
    printf("%lld",dp(1,k));
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值