背包问题求具体方案
题目描述
样例解释
我们可以选择第1个物品和第4个物品,价值是8,方案就是2 3;也可以选择第2个物品和第3个物品,价值也是8,方案就是1 4;但是由于是按照字典序从小到大,因此1 4的字典序是小于2 3的,因此,我们选择第1个物品和第4个物品,于是输出背包的方案就是1 4。
核心思路
问题:为什么是从后向前遍历物品呢?
首先要明确一点:其实从前往后遍历物品和从后往前遍历物品,最终得到的最优解都是相等的,但是这个题目由于要输出背包的具体方案,所以才会考虑到从后往前遍历。在我们之前的01背包中,是从前往后遍历物品,于是最后得到的 f [ n ] [ m ] f[n][m] f[n][m]就是最优解。
- 如果要求具体方案的问题,就必须通过状态的状态转移等式去找路径,这里要找的是字典序最小,即如果存在靠前的物品可以选,那我们就尽可能的选靠前的物品
- 从1枚举到N,和从N枚举到1,计算背包能装的总价值最大,其实求出来的结果是一样的,只是选的顺序不一样,可是题目要求是字典序最小,因此从N枚举到1可以计算出 f [ 1 ] [ m ] f[1][m] f[1][m]是背包的最大价值,由于该状态是由第二个物品选和不选两个状态转移过来的,即考虑 f [ i ] [ j ] f[i][j] f[i][j]是怎么来的,是由上一个物品选或者不选得来的,即从 f [ i + 1 ] [ j ] f[i+1][j] f[i+1][j]或者 f [ i + 1 ] [ j − v [ i ] ] + w [ i ] f[i+1][j-v[i]]+w[i] f[i+1][j−v[i]]+w[i]转移过来的,因此就可以通过当前的最优值,找到上一个的最优值,就可以知道上一个最优值对应的物品到底有选还是没选
- 由于要求字典序最小,因此需要从第一个物品的最优状态值开始往第二,第三个物品…的最优状态值找
问题:如何理解若 f [ i ] [ j ] f[i][j] f[i][j]能从 f [ i + 1 ] [ j − v [ i ] ] f[i+1][j-v[i]] f[i+1][j−v[i]]转移得到,则一定要选第 i i i个物品呢?
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N]; //v是体积 w是价值
int f[N][N]; //f[i][j]表示从第i个物品到最后一个物品 装入容量为j的背包的最优解
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
//逆序取物
for(int i=n;i>=1;i--)
{
//枚举背包容量 这里j从0开始或者从1开始都是可以的
//只不过从0开始,那么f[i][0]=f[i+1][0]=0,且不满足j>=v[i],因此没有必要让j从0开始
for(int j=1;j<=m;j++)
{
//对于第i件物品,我想选就选,不想选就不选,因此对于这件物品,"选择"是没有限制要求的
//因此肯定由f[i][j]=f[i+1][j]这种情况
f[i][j]=f[i+1][j];
//但是对于如果我想选这件物品,就必须保证此时的背包容量j是>=这件物品的体积的
//不然背包放不下呀
if(j>=v[i])
f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
}
}
//经过上面的逆序取物的01背包操作后,我们让最优解落到了f[1][m]中
//因此当我们从第一个物品开始出发去找最优解的路径时,那么此时背包容量应该为j
int j=m; //剩余的背包质量
//去寻找最优解的路径
for(int i=1;i<=n;i++)
{
//若f[i][j]能通过f[i+1][j-v[i]]得到,则一定要选第i个物品
if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])
{
printf("%d ",i); //输出选择的这件物品
j-=v[i]; //由于选择的是这件物品,因此背包的剩余质量就是j-v[i]
}
}
return 0;
}