背包九讲之有依赖的背包问题

有 N 个物品和一个容量是 V 的背包。

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。

如下图所示:
在这里插入图片描述

如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。

接下来有 N 行数据,每行数据表示一个物品。
第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。

输出格式
输出一个整数,表示最大价值。

数据范围
1≤N,V≤100
1≤vi,wi≤100
父节点编号范围:

内部结点:1≤pi≤N;
根节点 pi=−1;
输入样例

5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2

输出样例:

11

代码

#include <iostream>
#include<vector>
using namespace std;
int f[105][105];//f[x][v]表达选择以x为子树的物品,在容量不超过v时所获得的最大价值
vector<int> son[105];//son[i]储存第i件物品所有的子结点编号
int v[105],w[105],n,m,root;//v[i],w[i]分别表示第i件物品的体积和价值,n,m,root分别表示物品个数、背包容量和根结点的序号。
void dfs(int x){
    for(int i=v[x];i<=m;i++)//物品x必须选,所以f[x][v[x] ~ m]= w[x]都初始化为物品x的价值
        f[x][i]=w[x];
    for(int i=0;i<son[x].size();i++){//遍历所有以x为根的子树
        int s=son[x][i];//获取x的第i个子结点的序号
        dfs(s);//对其第i个子结点进行深度优先遍历,因为父结点的计算要使用子结点的结果,所以要先填子结点的
        //注意:不能写成(j=v[x];j<=m;j++),因为较大的容量要用到较小容量的初始值而不是较小容量的最优解
        for(int j=m;j>=v[x];j--){//j的范围为v[x]~m, 小于v[x]无法选择以x为子树的物品
            for(int k=0;k<=j-v[x];k++){//分给子树s的空间不能大于j-v[x],不然都无法选根物品x
                //求在只选第x件物品,选第x件物品后还剩容量k去选取其子结点构成子树的最大值
                //f[s][k]表示选第x件物品后还剩容量k去选取其子结点构成子树的物品
                //因为对于有同一个父结点的多个子结点可以同时选择,所以f[x][j-k]可能已经是选择了某个子结点的最优解
                //比较选或不选该子结点那个方案更优,这是01背包问题,所以j要从大到小循环
                //因为如果j从小到大循环的话,变成完全背包问题,可能在较小的j中已经选择了该子结点,可能会导致该子结点的重复选择
                f[x][j]=max(f[x][j],f[x][j-k]+f[s][k]);
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int p;
        cin>>v[i]>>w[i]>>p;//输入第i件物品的体积、价值和父结点编号
        if(p==-1){
            root=i;//记录根结点的序号
        }else{
            son[p].push_back(i);//将第i件物品加到其父结点p的子结点集合中
        }
    }
    dfs(root);//对根进行深度优先遍历,
    cout<<f[root][m]<<endl;//f[root][m]表达选择以根为子树的物品,在容量不超过m时所获得的最大价值(即答案)
    return 0;
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值