又鸽了很久……html
博主刚刚学会背包问题不久,而后有一段时间没练习了ios
今天就来从新温习一下,顺手就写了这一篇博客。算法
好了,下面进入正题!数组
背包问题是动态规划的一个分支函数
主要是分红了01背包、彻底背包和多重背包。优化
下面从01背包开始讲解。spa
01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。01背包可谓是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,而且有权值和体积两个属性。在01背包问题中,由于每种物品只有一个,对于每一个物品只须要考虑选与不选两种状况。若是不选择将其放入背包中,则不须要处理。若是选择将其放入背包中,因为不清楚以前放入的物品占据了多大的空间,须要枚举将这个物品放入背包后可能占据背包空间的全部状况。code
有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可以使价值总和最大。htm
了解了基本概念和问题雏形后咱们就能够来想作题的方法了。blog
从题目里看,咱们就能看出,01背包有个特色:每种物品仅有一件,能够选择放或不放。
因此咱们就能够把每种状况都枚举一遍。
首先创建一个二维数组dp[][]表示价值,w[i]是每件物品的价值,c[i]是每件物品的体积
而后就想,因为它只有放和不放两种状态,咱们就要比较这两种状态的价值,用max函数。
状态转移方程:dp[i][v]=max{dp[i-1][v],dp[i-1][v-c[i]]+w[i]}
其中,dp[i-1][v]表示不放该物品,dp[i-1][v-c[i]]+w[i]表示放入该物品。
这样作一个循环枚举各类状况便可。
for (i = 1; i <= n; i++)
for (j = v; j >= c[i]; j--)//在这里,背包放入物品后,容量不断的减小,直到再也放不进了
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i]);
最后的结果就是最大值。
还有一些优化的方法:01滚动和就地滚动。
01滚动:
咱们能够看到每一行的结果实际上只与上一行有关,因此就能够01滚动——f[i][0,1] 一行记录前一行的值,另外一行记录当前行的值……
因此,这是一种简化的好办法!
就地滚动:
就地滚动就是用一个一维数组,把以前的状态和当前的状态放在同一个数组,可是在写的过程当中会有问题。
先上代码吧:
for(i=1 ; i<= n ; i++)
for(j= c[i]; j<v ; j++) if(!dp[j-c[i]) dp[j] = dp[j-c[i]];
咱们会发现,这样的话一个物品会被重复计算屡次。
问题1:采药
这是一个经典的问题哦!
飞机场:洛谷P1048 采药
问题解答(不可用于直接AC本题,可进行参考!)
1 #include<iostream>
2 #include<cstdio>
3 #include<cmath>
4 #include<cstring>
5 #include<algorithm>
6 #define inf 100000000
7 //状态:dp[i][j]表示考虑前i个草药,目前体积之和为j,能够得到的最大价值
8 //转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
9 //答案:dp[m][max(...)]
10 //不须要初始化,直接算
11 //复杂度:O(m*t)
12 int dp[105][1005],w[105],v[105];
13 using namespace std; 14 int main() 15 { 16 int t,m; 17 cin>>t>>m; 18 for(int i=1;i<=m;i++) 19 { 20 cin>>w[i]>>v[i]; 21 } 22 for(int i=1;i<=m;i++) 23 for(int j=t;j>=0;j--) 24 { 25 if(j-w[i]>=0) 26 dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); 27 else 28 dp[i][j]=dp[i-1][j]; 29 } 30 int ans=0; 31 for(int i=0;i<=t;i++) 32 { 33 ans=max(dp[m][i],dp[m][i-1]); 34 } 35 cout<<ans; 36 return 0; 37 }
固然还有一种作法。能够直接使用一维数组:参见一本通。
这个问题很是相似于01背包问题,所不一样的是每种物品有无限件,也就是从每种物品的角度考虑,与它相关的策略已并不是取或不取两种,而是有取0件、取1件、取2件……取[V/c]件等不少种。
有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。放入第 i 种物品的费用是 Ci ,价值是 Wi 。求解:将哪些物品装入背包,可以使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。
这个题目有一个和01背包不同的地方:每种物品有无数件!
而后咱们想前面我说过的就地滚动,会计算屡次,这不正巧?
问题2:疯狂的采药
这个……这个题目的介绍不大正经,未成年人请在家长的陪伴下观看。
飞机场:洛谷P1616 疯狂的采药
问题解答(不可用于直接AC本题,可进行参考!)
1 #include<iostream>
2 #include<cstdio>
3 #include<cmath>
4 #include<cstring>
5 #include<algorithm>
6 #define inf 100000000
7 //状态:dp[i][j]表示考虑前i个草药,目前体积之和为j,能够得到的最大价值
8 //转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
9 //答案:dp[m][max(...)]
10 //不须要初始化,直接算
11 //复杂度:O(m*t)
12 int f[1000001],t,m,ti,v;
13 using namespace std; 14 int main() 15 { 16 cin>>t>>m; 17 for(int i=1;i<=m;i++) 18 { 19 cin>>ti>>v; 20 for(int j=ti;j<=t;j++) 21 f[j]=max(f[j],f[j-ti]+v); 22 } 23 cout<<f[t]; 24 return 0; 25 }
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件体积是w[i],价值是v[i]。求解将哪些物品装入背包可以使这些物品的费用总和不超过背包容量,且价值总和最大。
这个问题的特色是:每种物品有必定数量
这个运用的是二进制思想!
转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则获得了物品数为Σn[i]的01背包问题。
咱们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换之后的物品。
另外,取超过n[i]件的策略必不能出现。 方法是:将第i种物品分红若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。
使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是知足n[i]-2^k+1>0的最大整数。例如,若是n[i]为13,就将这种物品分红系数分别为1,2,4,6的四件物品。
解决问题的道理
1) 1+2+4+...+2^(k-1)+n[i]-2^k+1 = n[i] 这就保证了最多为n[i]个物品
2)1,2,4,……,2^(k-1),能够凑出1到2^k – 1的全部整数(联系一个数的二进制拆分便可证实,证实过程在下面的题解中)
3) 2^k……n[i]的全部整数能够用若干个上述元素凑出(能够理解为凑n[i]-t, 而n[i]为上面全部数的和,t则是一个小于2^k 的数,那么在全部的数中去掉组成2^k 的那些数剩下的就能够组成n[i]-t了)
固然,这个二进制的道理我在以前的一篇博客上写过,一会的实战演练会带大家去。
问题3:宝物筛选
这是一道很水的蓝题……
飞机场:洛谷P1776 宝物筛选题解(我写的,大家还有更详细的多重背包解决思路)
本文就写这么多了,背包问题在考试中会常常出现,但愿你们深刻理解其中的思想。
客官,给个赞再走呗?