动态规划总结(01背包 完全背包 多重背包)
动态规划总结(01背包 完全背包 多重背包)
一、学习资料
1.UVA DP 入门专题
2.夜深人静写算法(二) - 动态规划
3.算法之动态规划
4.什么是动态规划?动态规划的意义是什么?
5.01背包问题和完全背包问题
6.背包九讲
二、练习题目
1.01背包练习
2.完全背包 多重背包练习
3.UVA的部分题目
三、模型总结
(1)01背包问题
1.模型大意
有n件商品,每件商品仅有一件,并且每件商品有自己的价值v和重量w。现在有一个最大承载重量为m的背包,求解最多能装下价值为多少的商品。
2.状态转移方程
dp[j] = max(dp[j],dp[j-w[i]]+v[i])
3.核心代码片
for(int i =1; i<=n;++i){
for(int j = m; j>=w[i];--j)
dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
4.习题小结
1).HDOJ.2955
结合了概率和独立事件的知识。对于每个银行,小偷有抢或者不抢两种选择,并且每个银行抢不抢,事件发生时独立的。两个独立事件共同发生的概率,为两个事件发生概率的乘积。这是需要注意的。 另外一点就是,在遇到一些概率的问题的时候,通常需要将其转换成其对立事件发生的概率。这样解决起来较为容易。
2).HDOJ.3466
题面本身就是一个简单的01背包问题但是关键在于本题却需要排序,原因是会影响到后续物品的选择。简单来说,破坏了动态规划的无后效性。
对于每个商品,尤其能够买的临界值qi和其本身的价格pi,即当手中的钱不够qi的时候,商人就不考虑把第i件商品卖给你了。 比如有以下商品:
label | item1 | item 2 |
---|---|---|
q | q1 | q2 |
p | p1 | p2 |
其中 q1 – p1 < q2 – p2
若想把这两件商品全部购买,则至少需要p1+p2的金钱。那么如果我们没有这么多金钱怎么办。这就要考虑到dp时候的状态转移了。
在状态转移的时候,item1能从item2转移的状态区间是[min(q1+p2,m),m],同理,item2能从item1转移的状态区间是[min(q2+p1,m),m]。对于p1+q2或p2+q1,个人的理解是,先买了物品1花费p1,因为现在要对物品2进行状态转移,则需要钱的数量大于q2才可以,否则无法进行状态转移,即物品1的状态没有得到利用,因为我们要尽可能重复的利用这个区间。(p2+q1同理可得)
故对于以上2件商品,我们要看p1+q2和p2+q1孰大孰小。再根据题目数据不难看出 p1 + q2 < p2 + q1。
根据以上的叙述不难得出结论:要按qi-pi的差值升序排序,然再做01背包。那么疑问也油然而生:为什么平常的做01背包也没见排序呀? 这是因为普通的01背包pi==qi,也就是说只要有足够的金钱,就能购买物品。故不需要排序。所以以后做这种类型的dp要留个心眼。
3).HDOJ.2546
这题跟上面的那个题目也有相似之处,由于此题恒定q为5,我们可以采用别的方法来处理。先按照价格降序/升序排序,然后把价格最大的那个留下来(留给那个剩余的5块钱,一边让剩余金额最小)。然后以总金额-5为背包容量,除去最贵的剩余菜为商品做01背包即可。
4).HDOJ.2639
此题本身依旧是一个01背包,但是题目要求输出的是第k最优解,平常题目都是直接输出最优解即可。
根据背包九讲,不难想到肯定要增加一维,使得dp变为二维数组,其中第二维用来表示第K最优解。其次分别2个辅助数组a[],b[],容量以题目中的K为上限。实现方法如下:
a.在做01背包的时候,多增加一层循环,由1->k的循环,用来储存第K个解。将原本的01背包状态转移方程中较大的放到a[]中,较小的放到b[]中。
b.其次对a[],b[]做处理,依次从这2个数组中取出较大的那个,放到dp[i][k].表示第k解。
代码如下:
for(int i = 1;i<=n;++i){
for(int j =m;j>=w[i];--j){
int l;
for( l = 1;l<=k;l++){
a[l] = dp[j-w[i]][l]+v[i];
b[l] = dp[j][l];
}
a[l] = b[l] = -1;
int x,y,z; x = y= z = 1;
while(z<=k && (a[x]!=-1 || b[y]!=-1)){
if(a[x] > b[y]){ dp[j][z] = a[x];x++;}
else {dp[j][z] = b[y];y++;}
if(dp[j][z] != dp[j][z-1]) z++;
}
}
}
5.总结
1). 不要忘记初始化dp数组,和其他的辅助数组。
2). 在求一些最大最小值,或者是否可以构成的时候,经常讲dp整个初始化为INF,或者将dp[0]初始化为0或者1,这个要具体情况具体分析。
(2)完全背包问题
1.模型大意
有n件商品,每件商品有无数件,并且每件商品有自己的价值v和重量w。现在有一个最大承载重量为m的背包,求解最多能装下价值为多少的商品。
2.状态转移方程
dp[j] = max(dp[j],dp[j-w[i]]+v[i])
3.核心代码片
for(int i =1; i<=n;++i){
for(int j = w[i]; j<=m;++j)
dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
4.习题小结
1).UVA.674
经典的硬币组成问题,问某个金额的组成方案有多少种,注意当数据特别大的时候要开long long数组。
2).HDOJ.2159
这题是带个数限制的完全背包,那么最先想到的就是增加一维来表示个数。
for(int i =1; i<=k; ++i) {
for(int j = a[i].ence; j<=m; ++j)
for(int l = 1; l<=s;++l)
dp[j][l] = max(dp[j][l],dp[j-a[i].ence][l-1] +a[i].exp);
}
5.总结
总的感觉完全背包没有什么难点,基本上就是套状态转移方程即可。
(3)多重背包问题
1.模型大意
有n件商品,第ai件商品有ci件,并且每件商品有自己的价值v和重量w。现在有一个最大承载重量为m的背包,求解最多能装下价值为多少的商品。
2.状态转移方程
**F[i,v] = max{F[i − 1, v − k ∗ Ci
] + k ∗ Wi
| 0 ≤ k ≤ Mi}**
3.核心代码片
一般集合二进制优化和单调队列优化,转换为01背包问题
4.习题小结
二进制优化
即用二进制来表示有限的状态。通俗点讲,就是用1,2,4,8,16……这样的数来表示任意一个有限数字。如3 = 2+1, 7=4+2+1 如果我们把物品的个数,拆分成1个2个4个……在一起的物品,那么就是相当于对这样的物品做出选择,即做01背包。 这样就由多重背包,转换成了01背包的问题。
二进制优化代码:
for(int i = 0 ;i<6;++i){
for(int j =1; j<=c[i]; j<<=1){
val[cnt] = j*(i+1);
num[cnt++] = j;
c[i]-=j;
}
if(c[i]>0){
val[cnt] = c[i] * (i+1);
num[cnt++] = c[i];
}
}
5.总结
一般大部分题目都是使用二进制优化转换成01背包问题,或者是使用单调队列优化,极大提高程序效率。
(4)LCS问题 – 最长公共子序列
1.模型大意
给出2个字符串,求出这两个字符串的最长公共子序列的长度。
2.状态转移方程
if(c1[i] == c2[j]) dp[i][j] =dp[i-1][j-1]+1;
else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
3.核心代码片
for(int i =1; i<=len1;++i){
for(int j = 1; j<=len2;++j){
if(c1[i] == c2[j]) dp[i][j] =dp[i-1][j-1]+1;
else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
(5)LIS问题 – 最长上升子序列
1.模型大意
给出一个序列,求出他的最长上升子序列的长度。
2.状态转移方程 + 核心代码片
for(int i = 2; i<=n;++i){
if(a[i]>dp[len]) dp[++len] = a[i];
else{
int pos = BS(dp,a,1,len,i);
dp[pos] = a[i];
}
}
int BS(int dt[],int t[],int left, int right,int i)
{
int mid;
while(left<right){
mid = (left+right)/2;
if(dt[mid]>=t[i]) right = mid;
else left = mid+1;
}
return left;
}