序:部分背包
这一部分的内容是大家再熟悉不过的了
我举个栗子,
我想学过贪心的差不多都做过这道题
这一类问题有一个特点,就是物品可以分割(或者是可以选择物品的局部)
作为#round 3的序,我希望看过这篇博文的人能够认清这一类问题与后面的各类背包问题的区别
1.01背包
愉快的吃药就从现在开始啦!
按照套路,那个人应该出现了,不是么?
:哇,居然被你猜对了
我想,我宁愿自己猜错了
:我今天又有一个问题,你能帮帮我吗?
(内心:多少金币?)
:不过这次没有金币。
(我是拒绝的)什么问题?
:我今天又去了超市,看中了一些喜欢的东西,想全部买下来,钱是够,可是发现好像带不了那么多,我想请你帮帮忙,看看我怎样才能获得最大的价值。
巧了,这不是今天学的背包么?
哈哈,正好能够大显身手了,我们跟他去看看吧
(超市)
:你看,就是这些,这些,还有这些,哦对了,还有那些……
哇,这东西好像有点多,不过每个都只有一件,我们可以好好地看一看,帮他解决
我们看看啊:
商品名称 | 枣药丸 | 键盘 | 笔记本(不是电脑) | 鹊巢牌咖啡 | WEY神犇亲笔签名 |
重量 | 1 | 3 | 2 | 1 | 1 |
价值 | 5 | 8 | 1 | 2 | 99999999999 |
:我最多能带总重量$\leqslant3$的东西,超过就带不走了,啊啊啊啊啊啊啊啊啊啊啊我都想要啊……
我们先不理他,自己分析一下这道题目
恩...应该就是让我们在这里面选出一些东西总重量在3以内的东西,并使得它们的价值最大
这摆明了一个01背包嘛!我们就套进来试试吧
先设一个$w和c$两个数组用$w[i]$表示价值,用$c[i]$表示重量
再设一个$dp$二维数组并使$dp[i][j]$表示前$i$件物品正好放入容量为$j$的背包能够获得的最大价值
我们得到了两种不同的选择
一种是选择不放这个物品$dp[i][j]=dp[i-1][j]$
另一种是选择放这个物品$dp[i][j]=dp[i-1][j-c[i]]+w[i]$
然后把两个弄在一起,取最大值$max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])$
这就是状态转移方程了~
顺便把边界条件弄出来
$dp[0][0]=0$
$dp[i][j]=-INF$
可爱的程序↓
1 #include2 #define fp(i,l,r) for(register int i=(l);i<=(r);++i) 3 #define fd(i,l,r) for(register int i=(l);i>=(r);--i) 4 using namespace std; 5 const int INF=0xfffffff; 6 inline int botposs(int a,int b,int pd){ 7 if(pd==1) return a>b?a:b; 8 if(pd==0) return a>b?b:a; 9 }10 int main(){11 int dp[1000+20][1000+20];12 int w[1000+20],c[1000+20];13 int n,v;14 int ans=-INF;15 scanf("%d%d",&v,&n);16 fp(i,1,n){17 scanf("%d%d",&c[i],&w[i]);18 }19 dp[0][0]=0;20 fp(i,1,n){21 fp(j,1,n){22 dp[i][j]=-INF;23 }24 }25 fp(i,1,n){26 fp(j,0,v){27 if(j>=c[i]){28 dp[i][j]=botposs(dp[i-1][j],dp[i-1][j-c[i]]+w[i],1);29 }30 else{31 dp[i][j]=dp[i-1][j];32 }33 ans=botposs(ans,dp[n][j],1);34 }35 }36 printf("%d",ans);37 return 0;38 }
本代码参考(别干坏事啊)
:谢谢你啊,我成功了,不过我昨天突然想开商店,进了一批商品,好像有几千种,你能继续帮我吗?
...如果有几千种的话,那么我们之前的二维数组$dp$就会失灵啦!
怎么办呢?
我们来优化呗,记得数字三角形这个动态规划最经典的题目种中,我们学会了滚动数组
这题好像也可以用,因为每一行的状态都是由上一行的状态得来的
不信你看$dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])$
对吧,于是我们就可以开一个小得多的数组即$dp[5000][2]$来完成委托了!
这个优化最重要的地方就在这俩:
$dp[0][j]=max{f[1][j],f[1][j-c[i]]+w[i]}$
$dp[1][j]=max{f[0][j],f[0][j-c[i]]+w[i]}$
用%法就能够解决了!
代码就不放出来了
:你那个方法没有成功呢...
啊?这不C++科学
那我们就只能压维啦!
想办法把$dp[5000][2]$变成$dp[5000]$
怎么办呢?
其实这也不是不可能
我们只需要$dp[j]$表示容量不超过$j$的最大值就行了
于是我们可爱的状态转移方程就变成了$dp[j]=max{dp[j],dp[j-c[i]]+w[i]}$
初值是这样的$f[j]=0$
代码是这样的:
1 #include2 #define fp(i,l,r) for(register int i=(l);i<=(r);++i) 3 #define fd(i,l,r) for(register int i=(l);i>=(r);--i) 4 using namespace std; 5 inline int botposs(int a,int b,int pd){ 6 if(pd==1) return a>b?a:b; 7 if(pd==0) return a>b?b:a; 8 } 9 int main(){10 int dp[10000+20],w[10000+20],c[10000+20];11 int n,v;12 scanf("%d%d",&v,&n);13 fp(i,1,n){14 scanf("%d%d",&c[i],&w[i]);15 }16 fp(i,1,v){17 dp[i]=0;18 }19 fp(i,1,n){20 fd(j,v,c[i]){21 dp[j]=botposs(dp[j],dp[j-c[i]]+w[i],1);22 }23 }24 printf("%d",dp[v]);25 return 0;26 }
:谢谢你啦!
好啦,01背包的旅程就结束啦!(配合右下角“秋天”食用更佳)
2.完全背包
Warning:本章不会出现表情小人了!
我们来正经地偷窃讲述完全背包的知识吧
完全背包和01背包只有一个区别:物品可以取无限次
易证方程就是$dp[i][j]=max{dp[i-1][j-k*c[i]]+k*w[i]}$
$k$表示取某件物品的数量
其实只要你弄懂了01背包,这个方程你是能够秒懂的!
但是它的时间复杂度是$O(NV\sum_\frac{j}{c[i]})$
代码和01背包极其相似,就不放出来了
所以我们优化,将时间优化成$O(nv)$空间$O(v)$的玩意儿
1 #include2 #define fp(i,l,r) for(register int i=(l);i<=(r);++i) 3 #define fd(i,l,r) for(register int i=(l);i>=(r);--i) 4 using namespace std; 5 inline int botposs(int a,int b,int pd){ 6 if(pd==1) return a>b?a:b; 7 if(pd==0) return a>b?b:a; 8 } 9 int dp[100000+20],w[100000+20],c[100000+20];10 int main(){11 int n,v;12 scanf("%d%d",&v,&n);13 fp(i,1,n){14 scanf("%d%d",&c[i],&w[i]);15 }16 fp(i,1,n){17 fp(j,c[i],v){18 if(dp[j-c[i]]+w[i]>dp[j]){19 dp[j]=dp[j-c[i]]+w[i];20 }21 }22 }23 printf("%d ",dp[v]);24 return 0;25 }