动态规划专题
前言
太恐怖了,今天一复习动态规划,才发现我什么都不会,0/1背包都打不出来,还学什么斜率优化DP,四边形不等式DP,学个喘喘,so scary,到头来发现自己是小白,啥都不会啊!!!
所以,没有时间在等了,必须立刻安排复习
0/1 背包问题
http://oi.cdshishi.net/p/A1031
题目大意
有 \(n\) 个物体,背包大小为 \(m\),每个物体有一个大小 \(w[i]\),有一个价值 \(c[i]\),现在求背包里能装下的最大价值。
做题思路
动态规划数组 \(f[ i ][ j ]\) 表示装了 \(i\) 个物品,背包大小为 \(j\) 时的最大价值。
考虑如何转移:
即比较不装第 \(i\) 个物品和装第 \(i\) 个物品哪个贡献大,如果当前背包空间小于 \(w[i]\) (也就是装不下这个物品)那就不装。
最后答案在 \(f[n][m]\) 里面。
AC 代码
#include<bits/stdc++.h>
using namespace std;
int n,m,f[2000][2000];
int w[2000],c[2000];
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&w[i],&c[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j>=w[i]){
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
}
else f[i][j]=f[i-1][j];
}
}
printf("%d",f[n][m]);
return 0;
}
完全背包
在前行中认可自己,证明自己!
题目大意
相比于0/1背包只能选一个物体,完全背包的区别是每个物体有无数多个,给定背包大小,问能装下的最大价值。
做题思路
瞎做法: 开始自己沿着0/1背包的思路乱写了一个,没想到竟然A了,但空间和时间复杂度都没有正解优,时间复杂度应该是 \(O(nm^2)\),无奈这题 \(n,m\) 都只有200,所以过了,
正解思路: 只需要在0/1背包的代码上改动一个地方即可,即转移时如果选当前物体,还是从选到了 \(i\) 的这一当前层迭代,这样就达到了同一个物体选多次的目的了。
我只能说动态规划太奇妙,你可以盯着它的转移方程式半个小时也证不出它是正确的。(哭:
瞎搞核心代码:
for(int i=1;i<=n;i++){
for(int k=1;k*w[i]<=m;k++){
++cnt;
for(int j=1;j<=m;j++){
if(j>=w[i]){
f[cnt][j]=max(f[cnt-1][j],f[cnt-1][j-w[i]]+c[i]);
}
else f[cnt][j]=f[cnt-1][j];
ans=max(ans,f[cnt][j]);
}
}
}
正解代码:
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(w[i]<=j){
f[i][j]=max(f[i-1][j],f[i][j-w[i]]+c[i]);
}
else f[i][j]=f[i-1][j];
}
}
多重背包
题目大意
与上面不同的是,这次物品数量既不是一个,又不是无穷个,而是给定个。
做题思路
完了,什么玩意儿!啥啥二进制优化,让我好好研究一下。
好的,研究完了,十分的简单,就是把个数拆解成 \(1,2,4,8,16,32,64.....\) 的二进制倍数,这样任意组合即可组合出 \([1,x]\) 中的任意数,剩下的按0/1背包做就行。
核心代码:
for(int j=1;j<=n;j++){
int wi,ci,x;
scanf("%d%d%d",&wi,&ci,&x);
for(int i=1;i<=x;i=(i<<1)){
x-=i;
w[++cnt]=i*wi;
c[cnt]=i*ci;
}
if(x){
w[++cnt]=x*wi;
c[cnt]=x*ci;
}
}
for(int i=1;i<=cnt;i++){
for(int j=1;j<=m;j++){
if(w[i]<=j){
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
}
else{
f[i][j]=f[i-1][j];
}
}
}
printf("%d",f[cnt][m]);
背包问题到这里就基本结束了,是时候开始下一个专题了。
到时候补充一下把空间优化到一维的方法。