01背包问题
写在前面:
最近越来越忙了,主要原因是真的太菜了,做个题需要好久,这两天题涉及还蛮复杂,昨天是DFS与BFS搜索,我打算等有时间复习一下再一并写。
结果今天居然来了个DP,好家伙 又不会,只能一步一步来了。
今天的题是完全背包问题,后面再说,这里只记录01背包问题。主要学习状态转移函数等。
废话少说 直接放题:
01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
样例输入
4 5
1 2
2 4
3 4
4 5
样例输出
8
其实这道题的关键我已经标黑了,就是每个物品只能拿一个,不能重复拿。
主要关键就是这个F[i][j],它代表着只看前i个物品 总体积是j的情况下 总最大价值
//01背包问题-二维解法
//F[i][j] 表示只看前i个物品 总体积是j的情况下 总最大价值
#include<iostream>
using namespace std;
int arrW[1010];
int arrV[1010];
int F[1010][1010]; //状态转移
int main()
{
int N,V;
cin>>N>>V; //N个物品 V体积的背包
for(int i=1;i<=N;i++)
cin>>arrV[i]>>arrW[i]; //读入体积与价值
for(int i =1;i<=N;i++)
for(int j=0;j<=V;j++)
{
F[i][j]=F[i-1][j]; //不拿第i个物品
if(j>=arrV[i])
F[i][j]=max(F[i][j],F[i-1][j-arrV[i]]+arrW[i]); //拿了第i个物品
}
int res=0;
for(int i =0;i<=V;i++)
res=max(res,F[N][i]);
cout<<res<<endl;
return 0;
}
这里直接贴入样例输入,打印F[i][j]看看是什么东西,简单明了。
第0行 第0列 全是0代表我们没有空间或者没有物品的情况下什么都没有自然就是0
当有1个空间 1个物品时 就是2 代表我们拿了第一个物品
当有2个空间 2个物品时 就是4 代表拿的第二个物品
//F[i][j] 表示只看前i个物品 总体积是j的情况下 总最大价值
F[i][j]=F[i-1][j]; //不拿第i个物品
F[i][j]=max(F[i][j],F[i-1][j-arrV[i]]+arrW[i]);//拿
再看这代码就明白了 如果要拿第i个物品
就应该满足在i之前的空间足够放i物品情况下的最大价值来拿
例如目前为3空间 我可以拿两个物品:第二个物品它是2空间,4价值
那么就要找只有第一种物品的情况下空间为3-2=1此时的最大价值也就是2.
所以拿了之后的总价值是4+2=2。
不拿总价值就是2。
其实理解这个并不难,实际卡住我的是所谓的二维数组优化为一维 优化空间复杂度。
也就是下面的代码
//01背包问题-一维解法
#include<iostream>
using namespace std;
int arrW[1010];
int arrV[1010];
int F[1010]; //F[i]表示体积为i的情况下最大价值
int main()
{
int N,V;
cin>>N>>V; //N个物品 V体积的背包
for(int i=1;i<=N;i++)
cin>>arrV[i]>>arrW[i]; //读入体积与价值
//推f[j]时f[j-arrV[i]]保存的是状态f[i-1][j-arrV[i]]的值
for(int i =1;i<=N;i++)
for(int j=V;j>=arrV[i];j--)
{
F[j]=max(F[j],F[j-arrV[i]]+arrW[i]);
cout<<"j="<<j<<" F[j]="<<F[j]<<endl;
}
int res=0;
for(int i =0;i<=V;i++)
res=max(res,F[i]);
cout<<res<<endl;
return 0;
}
难点就在于下面这里
为什么第二个循环是逆着算的
其实可以这么来解释:如果采用顺序循环
当计算F[j]的时候右边需要计算F[j-arrV[i]]
例如:当我们算F[5]时第一次取物品时 它就需要计算 F[4],但F[4]在上一个循环已经计算了
它有可能已经拿了第i个物品了。
//逆序
for(int i =1;i<=N;i++)
for(int j=V;j>=arrV[i];j--)
F[j]=max(F[j],F[j-arrV[i]]+arrW[i]);
//顺序
for(int i =1;i<=N;i++)
for(int j=1;j<=V;j++)
{
F[j]=F[j];
if(j>=arrV[i])
F[j]=max(F[j],F[j-arrV[i]]+arrW[i]);
}
这里同样输出两种情况的过程看一下就知道了
首先是逆序的输出
然后是顺序
对于这个样例输入
当可拿物品只有1个 空间为2,3,4,5的时候 都重复的拿了第一个物品
就是因为我们计算F[2]的时候F[1]它已经拿了第1个物品了,此时再取1显然已经非法了。
而逆着算就正好避免了这一问题。
4 5
//左边空间 右边价值
1 2
2 4
3 4
4 5
好了,本篇文章就到这里,下次继续 完全背包 问题
需要提交代码测试的移步下面的网址:
在线判题网站: 01背包问题.