详细点击一下连接(背包九讲)html
http://love-oriented.com/pack/Index.html#sec1 如下内容,有些本身想法,有些摘录ios
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·································~~~~~~~~~~~~~~~~~~~~~~···································~~~~~~~~~~~~~~······················~~~~~~~~算法
动态规划算法可分解成从先到后的4个步骤:数组
1. 描述一个最优解的结构;ide
2. 递归地定义最优解的值;学习
3. 以“自底向上”的方式计算最优解的值;优化
4. 从已计算的信息中构建出最优解的路径。this
其中步骤1~3是动态规划求解问题的基础。若是题目只要求最优解的值,则步骤4能够省略。idea
每种物品能够选择放进背包或者不放进背包(这也就是0和1)spa
设背包容量为V,一共N件物品,每件物品体积为C[i],每件物品的价值为W[i]
1) 子问题定义:F[i][j]表示前i件物品中选取若干件物品放入剩余空间为j的背包中所能获得的最大价值。
2) 根据第i件物品放或不放进行决策
(1-1)
优化空间复杂度 -----要尝试理解,看了不少人的代码,都是用这个的
以上方法的时间和空间复杂度均为O(VN),其中时间复杂度应该已经不能再优化了,但空间复杂度却能够优化到O。
先考虑上面讲的基本思路如何实现,确定是有一个主循环i=1..N,每次算出来二维数组f[i][0..V]的全部值。那么,若是只用一个数组f[0..V],能不能保证第i次循环结束后f[v]中表示的就是咱们定义的状态f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来,可否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)可以获得f[i-1][v]和f[i-1][v-c[i]]的值呢?事实上,这要求在每次主循环中咱们以v=V..0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。伪代码以下:
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就至关于咱们的转移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]}
,由于如今的f[v-c[i]]就至关于原来的f[i-1][v-c[i]]。若是将v的循环顺序从上面的逆序改为顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它倒是彻底背包问题最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。
事实上,使用一维数组解01背包的程序在后面会被屡次用到,因此这里抽象出一个处理一件01背包中的物品过程,之后的代码中直接调用不加说明。
过程ZeroOnePack,表示处理一件01背包中的物品,两个参数cost、weight分别代表这件物品的费用和价值。
procedure ZeroOnePack(cost,weight)
for v=V..cost
f[v]=max{f[v],f[v-cost]+weight}
注意这个过程里的处理与前面给出的伪代码有所不一样。前面的示例程序写成v=V..0是为了在程序中体现每一个状态都按照方程求解了,避免没必要要的思惟复杂度。而这里既然已经抽象成看做黑箱的过程了,就能够加入优化。费用为cost的物品不会影响状态f[0..cost-1],这是显然的。
有了这个过程之后,01背包问题的伪代码就能够这样写:
for i=1..N
ZeroOnePack(c[i],w[i]);
ps:初始化问题
(1):要求刚好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就能够保证最终获得的f[N]是一种刚好装满背包的最优解
初始化的f数组事实上就是在没有任何物品能够放入背包时的合法状态。若是要求背包刚好装满,那么此时只有容量为0的背包可能被价值为0的nothing“刚好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。
(2):若是并无要求必须把背包装满,而是只但愿价格尽可能大,初始化时应该将f[0..V]所有设为0
若是背包并不是必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,因此初始时状态的值也就所有为0了。
典例加深
51nod 1085 背包问题V1
第1行,2个整数,N和W中间用空格隔开。N为物品的数量,W为背包的容量。(1 <= N <= 100,1 <= W <= 10000)
第2 - N + 1行,每行2个整数,Wi和Pi,分别是物品的体积和物品的价值。(1 <= Wi, Pi <= 10000)
输出能够容纳的最大价值。
3 6
2 5
3 8
4 9
14
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 int value[105],tiji[105]; 5 int dp[10005],num,m,i,j; 6 int main(){ 7 cin>>num>>m; 8 for(i=0;i<num;i++) 9 cin>>tiji[i]>>value[i]; 10 memset(dp,0,sizeof(dp)); 11 for(i=0;i<num;i++) 12 for(j=m;j>=tiji[i];j--)8 13 dp[j]=max((dp[j-tiji[i]]+value[i]),dp[j]); 14 cout<<dp[m]; 15 return 0; 16 }
hdoj 2546 饭卡 (01背包变形)
1 #include <string.h> 2 #include <iostream> 3 #include <algorithm> 4 using namespace std; 5 int main(){ 6 int n,V, w[1005],dp[1005]; 7 while(cin>>n&&n){ 8 memset(dp,0,sizeof(dp)); 9 for(int i=1;i<=n;i++) 10 cin>>w[i]; 11 cin>>V; 12 sort(w+1,w+1+n); //从1开始 13 if(V<5) cout<<V<<endl; 14 else{ 15 for(int i=1;i<n;i++) //留一个名额 16 for(int j=V-5;j>=w[i];j--) //保留5元,用剩下的钱去买价值更大的菜 17 dp[j]=max(dp[j],dp[j-w[i]]+w[i]); 18 cout<<V-dp[V-5]-w[n]<<endl; //余额-最多能买的菜-最贵的菜 19 } 20 } 21 return 0; 22 }
poj 3624 Charm Bracelet(01背包)
Description
Bessie has gone to the mall's jewelry store and spies a charm bracelet. Of course, she'd like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a 'desirability' factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).
Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.
Input
* Line 1: Two space-separated integers: N and M
* Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di
Output
* Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints
Sample Input
4 6
1 4
2 6
3 12
2 7
Sample Output
23
1 /* G++ 2 这题数组必定要开大,但也不能太大,不然都wa 3 看了discuss中讨论 4 貌似记录数组(dp)的大小应该由M决定吧 5 6 W,D的大小是由N决定的 7 */ 8 #include <stdio.h> 9 #include <string.h> 10 #define M 14000 11 int dp[M]; 12 int main(){ 13 int n,m,v,w; 14 memset(dp,0,sizeof(dp)); 15 scanf("%d %d",&n, &m); 16 for(int i=1;i<=n;i++){ 17 scanf("%d %d",&w, &v); 18 for(int j=m;j>=w;j--){ 19 int temp=dp[j-w]+v; 20 if(temp>dp[j]) 21 dp[j]=temp; 22 } 23 } 24 printf("%d\n",dp[m]); 25 }
一个常数优化
前面的伪代码中有 for v=V..1,能够将这个循环的下限进行改进。
因为只须要最后f[v]的值,倒推前一个物品,其实只要知道f[v-w[n]]便可。以此类推,对以第j个背包,其实只须要知道到f[v-sum{w[j..n]}]便可,即代码中的
for i=1..N
for v=V..0
能够改为
for i=1..n
bound=max{V-sum{w[i..n]},c[i]}
for v=V..bound
这对于V比较大时是有用的。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·~~~~~~~~~~~~~~~~~·············································································································································································~~~~~~~~~~~~~~~~~~~~~~~~~·
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可以使这些物品的费用总和不超过背包容量,且价值总和最大。
与01背包不一样就是每种物品无限件可用,也就是从每种物品的角度考虑,与它相关的策略已并不是取或者不取两种了,而是有取0件,取1件,取2件
……等不少种。若是仍然按照解01背包时的思路,令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然能够按照每种物品不一样的策略写出状态转移方程,像这样:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
这跟01背包问题同样有O(VN)个状态须要求解,但求解每一个状态的时间已经不是常数了,求解状态f[i][v]的时间是O(v/c[i]),总的复杂度能够认为是O(V*Σ(V/c[i])),是比较大的。
因此要将01背包问题的基本思路加以改进
彻底背包问题有一个很简单有效的优化,是这样的:若两件物品i、j知足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。这个优化的正确性显然:任何状况下均可将价值小费用高得j换成物美价廉的i,获得至少不会更差的方案。对于随机生成的数据,这个方法每每会大大减小物品的件数,从而加快速度。然而这个并不能改善最坏状况的复杂度,由于有可能特别设计的数据能够一件物品也去不掉。
这个优化能够简单的O(N^2)地实现,通常均可以承受。另外,针对背包问题而言,比较不错的一种方法是:首先将费用大于V的物品去掉,而后使用相似计数排序的作法,计算出费用相同的物品中价值最高的是哪一个,能够O(V+N)地完成这个优化。这个不过重要的过程就不给出伪代码了,但愿你能独立思考写出伪代码或程序。
既然01背包问题是最基本的背包问题,那么咱们能够考虑把彻底背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选V/c[i]件,因而能够把第i种物品转化为V/c[i]件费用及价值均不变的物品,而后求解这个01背包问题。这样彻底没有改进基本思路的时间复杂度,但这毕竟给了咱们将彻底背包问题转化为01背包问题的思路:将一种物品拆成多件物品。
更高效的转化方法是:把第i种物品拆成费用为c[i]*2^k、价值为w[i]*2^k的若干件物品,其中k知足c[i]*2^k<=V
。这是二进制的思想,由于无论最优策略选几件第i种物品,总能够表示成若干个2^k件物品的和。这样把每种物品拆成O(log V/c[i])件物品,是一个很大的改进。
但咱们有更优的O(VN)的算法。
这个算法使用一维数组,先看伪代码:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
想必你们看出了和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。
如今关键的是考虑:为什么彻底背包能够这么写?
在次咱们先来回忆下,01背包逆序的缘由?是为了是max中的两项是前一状态值,这就对了。
那么这里,咱们顺序写,这里的max中的两项固然就是当前状态的值了,为什么?
由于每种背包都是无限的。当咱们把i从1到N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里咱们要添加的不是前一个背包,而是当前背包。因此咱们要考虑的固然是当前状态。
事实上,对每一道动态规划题目都思考其方程的意义以及如何得来,是加深对动态规划的理解、提升动态规划功力的好方法。
典例加深
51nod 换零钱(彻底背包)
输入1个数N,N = 100表示1元钱。(1 <= N <= 100000)
输出Mod 10^9 + 7的结果
5
4
1 /* 2 dp[i]表示钱i能换零钱的种类数, 3 那么每次换的时候有两种状况 4 dp[i]表示不换,dp[i-v[j]]表示换了, 5 其和即是答案,换与不换实际上是利用到了前边的 6 计算结果 7 */ 8 #include <iostream> 9 #include <stdio.h> 10 using namespace std; 11 const int maxn=100005; 12 const int mod=1e9+7; 13 int n; 14 long long dp[maxn]; 15 int v[13]={1,2,5,10,20,50,100,200,500,1000,2000,5000,10000}; 16 17 int main(){ 18 scanf("%d",&n); 19 dp[0]=1; 20 for(int j=0;j<13;j++) 21 for(int i=v[j];i<=n;i++) 22 dp[i]=(dp[i]+dp[i-v[j]])%mod; 23 printf("%I64d\n",dp[n]); //printf("%lld\n",dp[n]); 24 return 0; 25 }
hdoj 1114 Piggy-Bank(彻底背包)
1 #include <string.h> 2 #include <stdio.h> 3 #include <algorithm> 4 using namespace std; 5 6 int dp[1000005]; 7 8 int main() 9 { 10 int t; 11 int wa,wb,w; 12 int n,val[505],wei[505],i,j; 13 scanf("%d",&t); 14 while(t--) 15 { 16 scanf("%d%d",&wa,&wb); 17 w = wb-wa;//必须减去小猪自己重量 18 scanf("%d",&n); 19 for(i = 0;i<n;i++) 20 scanf("%d%d",&val[i],&wei[i]); 21 for(i = 0;i<=w;i++) 22 { 23 dp[i] = 10000000;//由于要求小的,因此dp数组必须存大数 24 } 25 dp[0] = 0; 26 for(i = 0;i<n;i++) 27 { 28 for(j = wei[i];j<=w;j++) 29 { 30 dp[j] = min(dp[j],dp[j-wei[i]]+val[i]); 31 } 32 } 33 if(dp[w] == 10000000) 34 printf("This is impossible.\n"); 35 else 36 printf("The minimum amount of money in the piggy-bank is %d.\n",dp[w]); 37 } 38 39 return 0; 40 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~···········································································································~~~~~~~~~~~~~~~~~~~~~~
多重背包问题
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可以使这些物品的费用总和不超过背包容量,且价值总和最大。
这题目和彻底背包问题很相似。基本的方程只需将彻底背包问题的方程略微一改便可,由于对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}
复杂度是O(V*Σn[i])。
另外一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则获得了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σ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的四件物品。
2^0+2^1+2^2+(2^3)>13
因此,13-2^0-2^1-2^2=6
这四个数能够组成13中任意一个数 7=6+1, 5=4+1……
分红的这几件物品的系数和为n[i],代表不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每个整数,都可以用若干个系数的和表示,这个证实能够分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,但愿你本身思考尝试一下。
这样就将第i种物品分红了O(log n[i])种物品,将原问题转化为了复杂度为<math>O(V*Σlog n[i])的01背包问题,是很大的改进。
下面给出O(log amount)时间处理一件多重背包中物品的过程,其中amount表示物品的数量:
procedure MultiplePack(cost,weight,amount)
if cost*amount>=V
CompletePack(cost,weight)
return
integer k=1
while k<amount
ZeroOnePack(k*cost,k*weight)
amount=amount-k
k=k*2
ZeroOnePack(amount*cost,amount*weight)
但愿你仔细体会这个伪代码,若是不太理解的话,不妨翻译成程序代码之后,单步执行几回,或者头脑加纸笔模拟一下,也许就会慢慢理解了。
51nod 1086 背包问题V2(多重背包)
第1行,2个整数,N和W中间用空格隔开。N为物品的种类,W为背包的容量。(1 <= N <= 100,1 <= W <= 50000)
第2 - N + 1行,每行3个整数,Wi,Pi和Ci分别是物品体积、价值和数量。(1 <= Wi, Pi <= 10000, 1 <= Ci <= 200)
输出能够容纳的最大价值。
3 6
2 2 5
3 3 8
1 4 1
9
1 //思路:二进制 + 01背包思想 2 #include <iostream> 3 #include<cstdio> 4 #include<cmath> 5 int w,va,c,w1[10010],va1[10010]; 6 int dp[50010]; 7 using namespace std; 8 9 int main() 10 { 11 int N,W; 12 int cnt=0;//二进制以后的物件个数 13 scanf("%d%d",&N,&W); 14 for(int i=1;i<=N;i++) { 15 scanf("%d%d%d",&w,&va,&c); 16 for(int j=1;;j*=2) { 17 if(c>=j){ 18 w1[cnt]=j*w; 19 va1[cnt]=j*va; 20 c-=j; 21 cnt++; 22 } 23 else { 24 w1[cnt]=c*w; 25 va1[cnt]=c*va; 26 cnt++; 27 break; 28 } 29 } 30 } 31 for(int i=0;i<cnt;i++){ 32 for(int j=W;j>=w1[i];j--) 33 dp[j]=max(dp[j],dp[j-w1[i]]+va1[i]); 34 }//一维 空间复杂度小 35 printf("%d\n",dp[W]); 36 return 0; 37 }