题目描述:01背包问题
有 N 件物品和一个容量是V的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi, wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
[HTML_REMOVED]
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8
基本思考框架
C++ 代码
#include<iostream>
#include<algorithm>
using namespace std;
/*
f[i][j]表示只看前i个物品,总体积是j的情况下,总价值最大时多少
result = max{f[n][0~v]} //考虑n个物品
枚举有两种情况(其实是递推式)
1.不选第i个物品 , f[i][j] = f[i-1][j];
2.选第i个物品 , f[i][j] = f[i-1][j-v[i]]+w[i];
那么最优解 f[i][j] = max(situ1 , situ2)
然后我们还要考虑初始化的问题
这里合法的初始化只有 f[0][0] = 0;
*/
const int N = 1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main()
{
cin>>n>>m;
for(int i = 1 ; i <= n ;i ++) cin>>v[i]>>w[i];
//枚举 前i个物品
//枚举 体积0~m
//都是从小到大递推
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j <= m ; j ++)
{
//枚举情况1
f[i][j] = f[i-1][j];
//枚举情况2 , 如果这个状态的前一个状态合法, 就递推过来 ,并筛选
//一定是j-v[i]>=0
if(j-v[i]>=0)
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
int res = 0;
for(int i = 0 ; i <= m ; i++)
{
res = max(res,f[n][i]);
}
cout<<res<<endl;
}
优化思路
我稍微修改了中间段代码,使其在样例输入的情况下输出更新次序:
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j <= m ; j ++)
{
if(j==0) printf("\n");
f[i][j] = f[i-1][j];
if(j-v[i]>=0&&f[i-1][j-v[i]]+w[i]>f[i][j])
printf("f[%d,%d]=f[%d,%d]+%d=%d ",i,j,i-1,j-v[i],w[i],f[i-1][j-v[i]]+w[i]);
else
printf("f[%d,%d]=f[%d,%d]=%d ",i,j,i-1,j,f[i][j]);
if(j-v[i]>=0)
{
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
然后有如下性质
于是我们可以这样优化代码,将语句等价变换,改动处看注释。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n,m;
int f[N];//改 f[N][N]->f[N]
int v[N],w[N];
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 = m ; j >= v[i] ; j --)//改了循环条件,j从大到小更新!!!!
{
//改,删除了:f[i][j] = f[i-1][j],因为f[j]=f[j]无意义
//改,删除了 if(j-v[i]>=0),将此处判断简化到for循环里
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
return 0;
}
为什么j要从大得到小更新
我先把核心代码修改成这样,让j从小到大更新,并且输出更新次序。让大家看看结果
for(int i = 1 ; i <= n ; i++)
{
puts("");
for(int j = v[i] ; j <= m ; j ++)//从小到大更新
{
if(f[j-v[i]]+w[i]>f[j])
printf("f[%d]=f[%d]+%d=%d ",j,j-v[i],w[i],f[j-v[i]]+w[i]);
else
printf("f[%d]=f[%d]=%d ",j,j,f[j]);
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
}
输出结果如下(依旧是样例输入数据),我们发现,此时的更新变成了”横向更新”而不是我们所期待的层层更新。所以这种写法显然是错误的。
[HTML_REMOVED]接着我们把循环语句改一下使j要从大得到小更新,再输出更新次序测试一下[HTML_REMOVED]
for(int j = m ; j >= v[i] ; j --)
输出
不难发现,此时变量更新间的迭代达到我们希望的层层更新
nb大佬真的牛掰,写得相当棒
牛逼,写得真好
为什么限定N=1010?