背包问题求具体方案
分解一下,背包问题求具体方案=先解决背包问题,再求具体放进背包什么物品。
1.01背包问题求最大价值
题目描述:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据:
4 5
1 2
2 4
3 4
4 6
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N]; //f[i][j]表示前i个物品,体积不超过j的最大价值
int w[N], v[N]; //w[i]表示第i个物品的价值,v[i]表示第i个物品的体积
int n, m; //n表示物品个数,m表示背包最大容积
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) //枚举物品
{
for (int j = 0; j <= m; j++) //枚举背包容积
{
f[i][j] = f[i - 1][j]; //不选第i个物品
if (j >= v[i]) //选或不选第i个物品
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
cout << f[n][m];
return 0;
}
2.求01背包问题最大价值的方案
上面求出了01背包的最大价值,那么在求最大价值的方案之前,先看一个表格,从表格出发来解决问题:
发现,f[i][j]由两个状态转移而来,分别是两个绿色的箭头,如果j<v[i],那么就只能从f[i-1][j]转移过来。
不同颜色的箭头表示不同‘类’,状态方程的转移只能从相同’类‘中转移。那么想要找到最大价值的方案。从最后黄颜色的格子往前推。
通过从前往后推,可以知道,有三种情况;
- f[i][j]由f[i-1][j]得到,即没有选第i件物品。
- f[i][j]由f[i-1][j-v[i]]+w[i]得到,即选择了第i件物品。
- 还有可能f[i-1][j]==f[i-1][j-v[i]]+w[i]。即选择第i件物品和不选择第i件物品的价值一样大
那么就可以通过这几点来写代码。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N]; //f[i][j]表示前i个物品,体积不超过j的最大价值
int w[N], v[N]; //w[i]表示第i个物品的价值,v[i]表示第i个物品的体积
int n, m; //n表示物品个数,m表示背包最大容积
int sum = 0;
void dfs(int i, int j) //n是物品编号,m是体积
{
//终止条件
if (j == 0)
{
sum++; //方案数加1
cout << endl;
return;
}
if (j < v[i]) //不能装下第i件物品,那么只能从上一层同一列转移过来(看图)
{
//那么就说明这个路线的方案没有第i件物品!
dfs(i - 1, j);
}
if (j >= v[i]) //如果可以装下第i件物品,那么就看怎么转移过来的。要么f[i-1][j],要么f[i-1][j-v[i]]+w[i]。
{
if (f[i - 1][j] == f[i - 1][j - v[i]] + w[i]) //两个状态转移过来都可以
{
dfs(i - 1, j);//要么不选i
//要么选i
cout << i << " ";
dfs(i - 1, j - v[i]); //要么选i
}
else
{
if (f[i - 1][j] > f[i - 1][j - v[i]] + w[i]) //不选i的方案
{
dfs(i - 1, j);
}
else //选i
{
cout << i << " ";
dfs(i - 1, j - v[i]);
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) //枚举物品
{
for (int j = 0; j <= m; j++) //枚举背包容积
{
f[i][j] = f[i - 1][j]; //不选第i个物品
if (j >= v[i]) //选或不选第i个物品
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
//从f[n][m]开始
dfs(4, 5);
cout << "一共有" << sum << "条路径!\n";
return 0;
}
以上就是最基本的求背包方案。但是通常不会这么简单
首先解释一下字典序最小:
就是比较字符串大小!
比如:
123和223,那么223>123.也就是说,从第一个字符开始比较ascil码,如果相同,继续比较下一个,一直到有一个字符不相同,那么ascil码值大的就是字典序大的一方。
如果要满足字典序最小,那么从物品编号1开始到n结束,每一次选择的编号都应该尽可能的小。
下面的图是逆序枚举物品求得的f[i][j]
还是分为两步,先求背包问题,再求方案
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N][N];
int w[N],v[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) //输入体积和价值(n件物品)
cin>>v[i]>>w[i];
for(int i=n;i>=1;i--) //逆序,这样最终f[1][m]就是表示的是前n件物品体积为m下的最大价值
{
for(int j=0;j<=m;j++)
{
f[i][j]=f[i+1][j]; //表示不选第i件物品
if(j>=v[i]) //当前容量可以装下当前物品
f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
}
}
int j=m;
//由于f[1][m]是最大价值,但是不知道选取了什么物品,所以要判断选取了什么物品
//如果第i件物品选了,那么f[i][j]==f[i+1][j-v[i]]+w[i],
//如果第i件物品没有选,那么f[i][j]==f[i+1][j];
for(int i=1;i<=n;i++)
{
if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i]) //能装就要装,保证不跳,越连续字典序越小
{
cout<<i<<' ';
j-=v[i];
}
}
return 0;
}