背包问题---01背包--完全背包--多重背包

详细点击一下链接(背包九讲)

http://love-oriented.com/pack/Index.html#sec1    以下内容,有些自己想法,有些摘录

 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·································~~~~~~~~~~~~~~~~~~~~~~···································~~~~~~~~~~~~~~······················~~~~~~~~

 

动态规划算法可分解成从先到后的4个步骤:

 

1. 描述一个最优解的结构;

 

2. 递归地定义最优解的值;

 

3. 以“自底向上”的方式计算最优解的值;

 

4. 从已计算的信息中构建出最优解的路径。

 

其中步骤1~3是动态规划求解问题的基础。如果题目只要求最优解的值,则步骤4可以省略。

 

 

背包---------01背包问题

每种物品可以选择放进背包或者不放进背包(这也就是0和1)

 

设背包容量为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

 
在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数)。求背包能够容纳的最大价值。
 
Input
第1行,2个整数,N和W中间用空格隔开。N为物品的数量,W为背包的容量。(1 <= N <= 100,1 <= W <= 10000)
第2 - N + 1行,每行2个整数,Wi和Pi,分别是物品的体积和物品的价值。(1 <= Wi, Pi <= 10000)
 
Output
输出可以容纳的最大价值。
 
Input示例
3 6
2 5
3 8
4 9
 
Output示例
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背包变形)

 

Problem Description
电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
 

 

Input
多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。

n=0表示数据结束。
 

 

Output
对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。
 

 

Sample Input
1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0
 
Sample Output
-45
32
 
 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背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为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)的算法。

 

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  换零钱(完全背包)

 

N元钱换为零钱,有多少不同的换法?币值包括1 2 5分,1 2 5角,1 2 5 10 20 50 100元。
 
例如:5分钱换为零钱,有以下4种换法:
1、5个1分
2、1个2分3个1分
3、2个2分1个1分
4、1个5分
(由于结果可能会很大,输出Mod 10^9 + 7的结果)

 

 
Input
输入1个数N,N = 100表示1元钱。(1 <= N <= 100000)
 
Output
输出Mod 10^9 + 7的结果
 
Input示例
5
 
Output示例
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(完全背包)

Problem Description
Before ACM can do anything, a budget must be prepared and the necessary financial support obtained. The main income for this action comes from Irreversibly Bound Money (IBM). The idea behind is simple. Whenever some ACM member has any small money, he takes all the coins and throws them into a piggy-bank. You know that this process is irreversible, the coins cannot be removed without breaking the pig. After a sufficiently long time, there should be enough cash in the piggy-bank to pay everything that needs to be paid.

But there is a big problem with piggy-banks. It is not possible to determine how much money is inside. So we might break the pig into pieces only to find out that there is not enough money. Clearly, we want to avoid this unpleasant situation. The only possibility is to weigh the piggy-bank and try to guess how many coins are inside. Assume that we are able to determine the weight of the pig exactly and that we know the weights of all coins of a given currency. Then there is some minimum amount of money in the piggy-bank that we can guarantee. Your task is to find out this worst case and determine the minimum amount of cash inside the piggy-bank. We need your help. No more prematurely broken pigs!
 

 

Input
The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg, that means 1 <= E <= F <= 10000. On the second line of each test case, there is an integer number N (1 <= N <= 500) that gives the number of various coins used in the given currency. Following this are exactly N lines, each specifying one coin type. These lines contain two integers each, Pand W (1 <= P <= 50000, 1 <= W <=10000). P is the value of the coin in monetary units, W is it's weight in grams.
 

 

Output
Print exactly one line of output for each test case. The line must contain the sentence "The minimum amount of money in the piggy-bank is X." where X is the minimum amount of money that can be achieved using coins with the given total weight. If the weight cannot be reached exactly, print a line "This is impossible.".
 

 

Sample Input
3
10 110
2
1 1
30 50
10 110
2
1 1
50 30
1 6
2
10 3
20 4
 
Sample Output
The minimum amount of money in the piggy-bank is 60.
The minimum amount of money in the piggy-bank is 100.
This is impossible.
 
 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背包问题

另一种好想好写的基本方法是转化为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(多重背包)

有N种物品,每种物品的数量为C1,C2......Cn。从中任选若干件放在容量为W的背包里,每种物品的体积为W1,W2......Wn(Wi为整数),与之相对应的价值为P1,P2......Pn(Pi为整数)。求背包能够容纳的最大价值。
 
Input
第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)
 
Output
输出可以容纳的最大价值。
 
Input示例
3 6
2 2 5
3 3 8
1 4 1
 
Output示例
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 }

转载于:https://www.cnblogs.com/z-712/p/7296151.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值