目录
多阶段动态规划问题
有一类动态规划可解的问题:它可以描述成若干个有序的阶段,且每个阶段的状态只和上一阶段的状态有关,一般吧这类问题成为多阶段动态规划问题。
01背包问题
01背包问题是这样的:有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大,其中每种物品都只有1件。
针对这个问题,令dp[i][v]表示前i件物品恰好装入容量为v的背包中所能获得的最大价值。
考虑对第i件物品的选择策略,有两种策略:
(1)不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值,也即dp[i-1][v]
(2)放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也即dp[i-1][v-w[i]]+c[i]
由于只有两种策略,且要求获得最大价值,因此:
上面这个就是状态转移方程,注意到dp[i][v]只与前面的状态dp[i-1][]有关,所以可以枚举i从1到n,v到0到v,通过边界dp[0][v]=0(即前0件物品放入任何容量为v的背包中都只能获得价值0)就可以把整个dp数组递推出来。而由于dp[i][v]表示的是恰好为v的背包中都只能获得价值0)就可以把整个dp数组递推出来。而由于dp[i][v]表示的是恰好为v的情况,所以需要枚举dp[n][v],取其最大值才是最后的结果。
for(int i=1;i<=n;i++){
for(int v=w[i];v<=V;v++){
dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]);
}
}
时间复杂度和空间复杂度都是.
注意到状态转移方程中计算dp[i][v]时总是只需要dp[i-1][v]左侧部分的数据,且当计算dp[i+1][]的部分时,dp[i-1]的数据又完全用不到了(只需要用到dp[i][]),因此不妨可以直接开一个一维数组dp[v](即把一维省去),枚举方向改变为i从1到n,v从V到0,这样状态转移方程改变为:
这样修改对应到图中可以这样理解:v的枚举顺序变为从右到左,dp[i][v]右边的部分为刚计算过的需要保存给下一行使用的数据,而dp[i][v]左上角的阴影部分为当前需要使用的部分。 将这两者结合一下,即把dp[i][v]左上角和右边的部分放在一个数组里,每计算出一个dp[i][v],就相当于把dp[i-][v]抹消,因为在后面的运算中dp[i-1][v]再也用不到了。这种技巧为滚动数组。
for(int i=1;i<=n;i++){
for(int v=V;v>=w[i];v--){
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
}
}
这样01背包问题就可以使用一维数组来解决了,空间复杂度为。
特别说明:如果是用二维数组存放,v的枚举是顺序还是逆序都无所谓;如果使用一维数组存放,则v的枚举必须是逆序。
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100;
const int maxv=1000;
int w[maxn],c[maxn],dp[maxn];
int main(){
int n,V;
cin>>n>>V;
for(int i=0;i<n;i++){
cin>>w[i];
}
for(int i=0;i<n;i++){
cin>>c[i];
}
for(int v=0;v<=V;v++){
dp[v]=0;
}
for(int i=1;i<=n;i++){
for(int v=V;v>=w[i];v--){
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
}
}
int max=0;
for(int v=0;v<=V;v++){
if(dp[v]>max){
max=dp[v];
}
}
cout<<max<<endl;
return 0;
}
对能够划分阶段的问题,都可以尝试把阶段作为状态的一维,这可以使我们更方便地得到满足无后效性的状态。如果当前涉及的状态不满足无后效性,那么不妨把状态进行升维,即增加一维或若干维来表示相应的信息,这样可能就满足无后效性了。
完全背包问题
完全背包问题的叙述如下:
有n种物品,每种物品的单件重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都有无穷件。
可以看出,完全背包问题和01背包问题的唯一区别就在于:完全背包的物品数量每种有无穷件,选取物品时对同一种物品可以选1件、选2件…只要不超过容量V即可,而01背包的物品数量每种只有一件。
同样令dp[i][v]表示前i件物品恰好放入容量为v的背包中能获得的最大价值。
和01背包一样,完全背包问题的每种物品都有两种策略,但是也有不同点。对第i件物品来说:
(1)不放第i件物品,那么dp[i][v]=dp[i-1][v],这步跟01背包一样。
(2)放第i件物品,这里的处理和01背包有所不同,因为01背包的每个物品只能选择一个,因此选择放第i件物品就意味着必须转移到dp[i-1][v-w[i]]这个状态;但是完全背包不同,完全背包如果选择放第i件物品之后并不是转移到dp[i-1][v-w[i]],而是转移到dp[i][v-w[i]],这是因为每种物品可以放任意件,放了第i件物品后还可以继续放第i件物品,直到第二维的v-w[i]无法保持大于等于0为止。
由上面的分析可以写出状态转移方程:
边界:dp[0][v]=0
而这个状态转移方程同样可以改写成一维形式,即状态转移方程:
边界:dp[v]=0
写成一维形式后和01背包完全相同,唯一的区别在于这里v的枚举顺序是正向枚举,而01背包的一维形式中v必须是逆向枚举。完全背包的一维形式代码如下:
for(int i=1;i<=n;i++){
for(int v=w[i];v<=V;v++){
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
}
}