问题简介:
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 v [ i ],价值是 w [ i ]
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解在不超过背包容量的情况下价值最大数。
每种物品有无限个(假设第 i 个背包有K个 意味着对于第 i 个背包我们就有K+1中选择)
直接上一维空间:
----------------------------------------------------------------------------------------------------------
符号说明:F [ i ]表示在使用体积 j 后 的Worth_Max;
--------------------------------------------------------------------------------------------------------
结果:
res=max ( f [ 0,1,……,m ] );
状态转移问题:
for (int i =0;i<n;++i) // 第一层循环,表示对于第 i 个背包处理
for (int j = m ; j>v [ i ] ; j - -)
for ( int k =0 ; k * v [ i ] < = j ;k ++)
f [ j ]= max ( f [ j ] , f [ j - k * v [ i ] + k * w [ i ] ;
--------------------------------------------------------------------------------------
状态分析:
for (int j=v [ i ];j<= m ; j++) //第二层循环表示对于第 i 个背包来说,可用空间的改变带来的状态变化;
f [ j ]=max ( f [ j ] , f [ j -v [ i ] ] + w [ i ] ]) ;,整个实际上是不怎么好的啊
状态方程说明:
第二层循环的处理:
因为若不改变第二层循环则原第二层循环为: for ( int j = m ; j > v [ j ] ; j --);
注意此时的这个的第 i 个背包的体积大于 0;此时第二次循环还是从大到小的话,那么我在进行状态转移的时候 ,计算 f [ j ] 的时候,f [ j ]=max ( f [ j ] , f [ j -v [ i ] ] + w [ i ] ]) ;
这个时候会有 f [ j -v [ i ] ] + w [ i ] ]) 实际上是一个新的状态,这个状态并不在 f [ j ]里面。这就相当于所有背包就只用了一次,并没有达到我无限次的想法;
----------------------------------------
基本思路
明显的动态规划问题:
即: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背包问题是十分必要的。