背包九讲_01背包问题

问题简介:

有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解在不超过背包容量的情况下价值最大数。

符号说明:F [ i ] [ j ]表示在已经使用体积 j 且装了 i 件物品时的总价值;

                  v [ i ]  表示第 i 件物品的体积;

                  w [ i ] 表示第 i 件物品的价值;

基本思路

明显的动态规划问题:

即:F [ i ] [ j ] 的状态仅仅与之间的一个状态有关;

状态方程:

                   1. 选第 i 件物品:F [ i ] [ j ] = F [ i ] [ j ]

                   2. 不选第 i 件物品:F [ i ] [ j ] =  F [ i-1 ] [ j -v [ i ] ] //  关于这个 j - v [ i ] 的说明,就是说在这个时候我不选第 i 件物品,自然这个空间就腾出来了

状态转移方程:

                   F [ i ] [ j ] = max {  F [ i ] [ j ]  ,F [ i-1 ] [ j -v [ i ] ]  } //  就是这两个方程中选一个大的出来就行

代码详述(优化之前):

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;//有 n 个背包,总体积为 m
int f[N][N];//存放状态,
int v[N], w[N];//分别表示第 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] );//注意使用max函数的时候是使用( ) 不是 { }
}
int res = 0;
for (int j = 1; j <= m; ++j)
res = max(res, f[n][j]);
cout << res << endl;
}
/*
搜索,动归问题都是这个状态问题,状态才是根本
在写状态转移方程的时候:
1.先找到到底什么才是限制条件,这个里面背包的体积才是限制条件,所以它就是状态转移方程里的那个变量;
2.找到第 i 个状态和第 i-1 个状态之间的关系啊;(特别注意是否需要预判)
*/

动态规划时间复杂度分析:

状态数:二维数组存放状态(因为每个状态都会检索所以N平方)

分析状态转移方程中对状态的处理,没有循环,即为线性处理,C

综上:C*N平方

优化空间复杂度

代码详述:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;//有 n 个背包,总体积为 m
int f[N];//存放状态,表示体积为N的时候,价值最大数
int v[N], w[N];//分别表示第 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 =m ; j >=v[i]; j--)
f[j] = max(f[i],f[j - v[i]]+w[i] );//注意使用max函数的时候是使用( ) 不是 { }
cout << f[m] << endl;
}
/*
搜索,动归问题都是这个状态问题,状态才是根本
在写状态转移方程的时候:
1.先找到到底什么才是限制条件,这个里面背包的体积才是限制条件,所以它就是状态转移方程里的那个变量;
2.找到第 i 个状态和第 i-1 个状态之间的关系啊;(特别注意是否需要预判)
*/

关于代码二的思路,下一次训练再写

-----------------------------------------------------------------------------------------------------------------------------------------------------------

以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。

先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1..N,每次算出来二维数组f[i][0..V]的所有值。那么,如果只用一个数组f [0..V],能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1] [v-c[i]]两个子问题递推而来,能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够得到f[i-1][v]和f[i-1][v -c[i]]的值呢?事实上,这要求在每次主循环中我们以v=V..0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i -1][v-c[i]]的值。伪代码如下:

for i=1..N

for v=V..0

f[v]=max{f[v],f[v-c[i]]+w[i]};

其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i- 1][v-c[i]]},因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是另一个重要的背包问题P02最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。

原文地址:https://www.cnblogs.com/WAsbry/p/12639994.html