二叉苹果树 - 二叉树树型DP

传送门
中文题面:

题目描述

有一棵苹果树,如果树枝有分叉,一定是分 2 叉(就是说没有只有 1 个儿子的结点,这棵树共有N 个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 4 个树枝的树:
2 5
\ /
3 4
\ /
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。

输入格式

第1行2个数,N 和Q(1<=Q<= N,1<N<=100)。N 表示树的结点数,Q 表示要保留的树枝数量。
接下来 N-1 行描述树枝的信息。
每行3个整数,前两个是它连接的结点的编号,第3个数是这根树枝上苹果的数量。
每根树枝上的苹果不超过30000个。

输出格式

一个数,最多能留住的苹果的数量。

样例数据 1

输入

5 2
1 3 1
1 4 10
2 3 20
3 5 20

输出

21

题目分析

此题是选课的简化版,因为规定了树是一颗二叉树,dp[i][j]表示以i为根节点的子树选择j条边的最大值,因为是棵树,所以可以将边权转移到点权上,剩下的就与选课一题异曲同工。
若选择当前节点:

  1. 此节点是根节点的话,左右儿子一共分担j个。枚举即可。
  2. 此节点不是根节点,左右儿子一共分担j-1个。
    若不选择当前节点:dp = 0。
    取较优值。

code

#include<bits/stdc++.h>
using namespace std;

const int N = 100;
int ecnt, adj[N + 5], go[N * 2 + 5], nxt[N * 2 + 5], val[N + 5], len[N * 2 + 5];
int fa[N + 5], ch[N + 5][2];
typedef long long ll;
ll dp[N + 5][N + 5];
int n, m;

inline void addEdge(int u, int v, int c){
    nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = c;
    nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = c;
}

inline void dfs(int u, int f){
    fa[u] = f;
    int cnt = -1;
    for(int e = adj[u]; e; e = nxt[e]){
        int v = go[e];
        if(v == f) continue;
        ch[u][++cnt] = v;
        val[v] = len[e];
        dfs(v, u);
    }
}

inline ll DP(int u, int k){
    if(u == 0) return dp[u][k] = 0;
    if(dp[u][k] != -1) return dp[u][k];
    
    dp[u][k] = 0;
    //选择这个
    for(int i = 0; i <= k - 1 + (u == 1 ? 1 : 0); i++){
        DP(ch[u][0], i);
        DP(ch[u][1], k - 1 + (u == 1 ? 1 : 0) - i);
        dp[u][k] = max(dp[u][k], 1LL * val[u] + dp[ch[u][0]][i] + dp[ch[u][1]][k - 1 + (u == 1 ? 1 : 0) - i]);
    }
    
    return dp[u][k]; 
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
    cin >> n >> m;
    for(int i = 1; i < n; i++){
        int x, y, c;
        cin >> x >> y >> c;
        addEdge(x, y, c);
    }
    dfs(1, 0);
//  for(int i = 1; i <= n; i++) cout<<i<<": "<<fa[i]<<" "<<ch[i][0]<<" "<<ch[i][1]<<" "<<val[i]<<endl;
    memset(dp, -1, sizeof dp);
    DP(1, m);
    cout << dp[1][m] << endl;
    return 0;
}

转载于:https://www.cnblogs.com/CzYoL/p/7532839.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值