题目链接:点击这里
【输出方案】
一般而言,背包问题是要求一个最优值,如果要求输出这个最优值的方案,可以参照一般动态规划问题输出方案的方法:记录下每个状态的最优值是由状态转移方程的哪一项推出来的,换句话说,记录下它是由哪一个策略推出来的。便可根据这条策略找到上一个状态,从上一个状态接着向前推即可。
还是以 01 背包为例,方程为 。
再用一个数组 :
- 设 表示推出 的值时是采用了方程的前一项(也即 )
- 设 表示采用了方程的后一项。
注意这两项分别表示了两种策略:未选第 个物品及选了第 个物品。
那么输出方案的伪代码可以这样写(设最终状态为 ):
i ← N
v ← V
while i > 0
if G[i, v] = 0
print 未选第 i 项物品
else if G[i, v] = 1
print 选了第 i 项物品
v ← sv − Ci
i ← i − 1
另外,采用方程的前一项或后一项也可以在输出方案的过程中根据
的值实时地求出来,也就是说,不必纪录
数组,将上述代码中的
改成
,
改成
也可。
【输出字典序最小的最优方案】
原文链接:https://www.acwing.com/solution/acwing/content/2687/
题目要求输出字典序最小的解,假设存在一个包含第 个物品的最优解,为了确保字典序最小那么我们必然要选第一个。那么问题就转化成从 这些物品中找到最优解。
之前的 记录的都是前 个物品总容量为 的最优解,那么我们现在将 定义为从第 个元素到最后一个元素总容量为 的最优解。接下来考虑状态转移:
两种情况,第一种是不选第 个物品,那么最优解等同于从第 个物品到最后一个元素总容量为 的最优解;第二种是选了第 个物品,那么最优解等于当前物品的价值 加上从第 个物品到最后一个元素总容量为 的最优解。
计算完状态表示后,考虑如何的到最小字典序的解。
首先 肯定是最大价值,那么我们便开始考虑能否选取第 个物品呢。
-
如果 ,说明选取了第1个物品可以得到最优解。
-
如果 ,说明不选取第一个物品才能得到最优解。
-
如果 ,说明选不选都可以得到最优解,但是为了考虑字典序最小,我们也需要选取该物品。
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N], w[N];
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 --)
{
for(int j = 0 ; j <= m ; j++)
{
f[i][j] = f[i + 1][j];
if(j >= v[i])
f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
}
}
int vol = m;
for(int i = 1 ; i <= n ; i++)
{
//如果是最后一个元素,特判一下,防止越界即可
if(i == n && vol >= v[i])
{
printf("%d ", i);
break;
}
if(vol <= 0)
break;
//判断下标是否越界
if(vol - v[i]>=0 && f[i][vol] == f[i + 1][vol - v[i]] + w[i])
{
printf("%d ", i);
vol -= v[i]; //选了第i个物品,剩余容量就要减小。
}
}
return 0;
}