p2014 树形DP 背包问题板子

题目链接

https://www.luogu.com.cn/problem/P2014

题意

给你若干先修课-课程的关系,保证课程最多有一个先修课。每个课有自己的学分,最多选m门,问最大得分。

思路

容易发现是个树形DP的题目。

我们考虑dp[x][i]为x结点最多选i门时最大收益,给出的图是一个森林,不妨建立虚拟源点0,答案即为dp[0][m]。

考虑如何转移,树形DP中则为如何统计子树数据得到当前数据。显然,我们是在不同的子树选择一个dp状态相加再加上根节点的值。也就是类似一个分组背包。对于求解每个状态dp[x],我们将自己的背包重量设置为i,我们将每个子树看成一个组,每个组里有若干物品,其价值为dp[son][j],重为j,每个组最多选一个。我们这样就可以利用完全背包问题的转移来进行考虑了。

回想完全背包(说来惭愧,我这也是看树形DP才爬回去学的),我们可以将01背包每个物品看作一组,要么选要么不选。当这个组扩充到若干个物品时,我们也只需要改一下他的决策过程即可。因为最多一组选一个,我们在内层决策时取一下max即可。也就是

枚举组号
    枚举容量(倒序)
        枚举物品
            类 01 转移方程,但是取MAX

回到这里,我们的枚举组号,就是枚举子树,这个在dfs最外层for就实现了。
中层枚举容量,那就是从m到0嘛,对应我们dp第二维的变量(也就是压缩空间后的完全背包第一维)
内层枚举物品,也就是枚举子树的状态咯,其价值为dp[son][j],重为j。

在处理完子树之后,我们还需要处理一下自己的学分。显然我们之前的dp方程第二维是没处理自己的,所以我们还需要转换一下

for(int t=m;t;t--)
	dp[x][t]=dp[x][t-1]+va[x];

注意如果是虚拟源点,因为我们不必选修这门“虚拟课”,也就无需再处理这一步了。

代码
#include<cstdio>
#include<iostream>
#include<iomanip>
#include<map>
#include<unordered_map>
#include<string>
#include<queue>
#include<stack>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib> 
#include<chrono>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl "\n"
//#define int long long
//#define double long double
using namespace std;
	typedef long long ll;
	const int maxn=405;
	const int inf=0x3f3f3f3f;
	int n,m,k;
    int dp[maxn][maxn];
    int va[maxn];
    vector<int>e[maxn];
    void dfs(int x,int fa){
        for(auto y:e[x]){
            if(y==fa)   continue;
            dfs(y,x);
            for(int i=m;~i;i--){
                for(int j=i;j;j--){
                    dp[x][i]=max(dp[x][i],dp[x][i-j]+dp[y][j]);
                }
            }
        }
        if(x)
            for(int t=m;t;t--)
                dp[x][t]=dp[x][t-1]+va[x];
    }
	signed main(){
        IOS
		#ifndef ONLINE_JUDGE
		    freopen("IO\\in.txt","r",stdin);
		    freopen("IO\\out.txt","w",stdout);
        #endif
		cin>>n>>m;
        for(int i=1;i<=n;i++){
            int u;
            cin>>u>>va[i];
            e[u].push_back(i);
            e[i].push_back(u);
        }
        dfs(0,-1);
        cout<<dp[0][m]<<endl;
	} 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值