有 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;
}