01背包、完全背包、多重背包

原文地址:http://blog.csdn.net/wzy_1988/article/details/12260343

前言

今天花了一下午加一晚上的时间,在九度oj才ac了一道简单的多重背包题目,之前没做过多重背包的题目,导致我做题时复杂化了,虽然是假期但是也不能这么浪费时间,果断总结一下,这里参考了dd_engi大牛的《背包问题九讲》,原文链接: http://love-oriented.com/pack/

01背包


题目

有N件物品和一个容量为V的背包。第i建物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大

思路

这是最基础的背包问题,特点是:每种物品只有一件,可以选择放或者不放

用子问题定义状态:即dp[i][j]表示前i件物品放入一个容量为j的背包可以获得的最大价值。则其状态转移方程为:

[cpp]  view plain  copy
 print ?
  1. dp[i][j] = max{dp[i - 1][j], dp[i - 1][j - c[i]] + w[i]}  

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来。这里详细解释一下:

将前i件物品放入容量为j的背包中这个子问题,若只考虑第i件物品的策略(放或者不放),那么就可以转换为一个只牵扯前i-1件物品的问题。
  • 如果不放第i件物品,那么问题就转换为前i-1件物品放入容量为j的背包中的最大价值,价值为dp[i - 1][j]
  • 如果放入第i件物品,那么问题就转换为前i-1件物品放入容量为j-c[i]的背包中,此时能获得的最大价值是dp[i-1][j-c[i]],再加上放入第i件物品获得的价值w[i]

优化空间复杂度

先考虑一下上面的状态转移方程如何实现,肯定有一个主循环i = 1...N,每次算出来二维数组dp[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]]的值。伪代码如下:

[cpp]  view plain  copy
 print ?
  1. for i  in 0 ... N  
  2.     for  v = V ... 0  
  3.         f[v] = max{f[v], f[v-c[i]] + w[i]}  


练习题目

题目: http://acm.hdu.edu.cn/showproblem.php?pid=2602

代码:

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. #include   
  4.   
  5. #define N 1010  
  6.   
  7. int value[N], volume[N], dp[N];  
  8.   
  9. // 0-1背包,优化空间  
  10. void dpPackage(int n, int v)  
  11. {  
  12.     int i, j;  
  13.   
  14.     memset(dp, 0, sizeof(dp));  
  15.   
  16.     for (i = 1; i <= n; i ++) {  
  17.         for (j = v; j >= volume[i]; j --) {  
  18.                 dp[j] = dp[j] > dp[j - volume[i]] + value[i] ? dp[j] : dp[j - volume[i]] + value[i];  
  19.         }  
  20.     }  
  21.   
  22.     printf("%d\n", dp[v]);  
  23. }  
  24.   
  25. int main(void)  
  26. {  
  27.     int i, t, n, v;  
  28.   
  29.     scanf("%d", &t);  
  30.   
  31.     while (t --) {  
  32.         // 接收参数  
  33.         scanf("%d %d", &n, &v);  
  34.   
  35.         for (i = 1; i <= n; i ++)    scanf("%d", value + i);  
  36.         for (i = 1; i <= n; i ++)    scanf("%d", volume + i);  
  37.   
  38.         // 0-1背包  
  39.         dpPackage(n, v);  
  40.     }  
  41.   
  42.     return 0;  
  43. }  


完全背包问题


题目

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价格是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大

思路

这个问题类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略 已非取或不取两种,而且右取0件、取1件、取2件...等很多种。如果仍然按照01背包的思路,令dp[i][v]表示前i种物品恰好放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程:

[html]  view plain  copy
 print ?
  1. dp[i][v] = max{dp[i-1][v - k * c[i]] + k * w[i] | 0 <= k * c[i]<= v}  

转化为01背包求解

最简单的想法是:考虑到第i种物品最多选V/c[i]件,于是可以把第i种物品转换为V/c[i]件费用及价值均不变的物品,然后求解这个01背包问题。但是这样完全没有改进时间复杂度,但这毕竟给了我们将完全背包转换为01背包问题的思路:将一种物品拆成多件物品

O(VN)的算法

这个算法使用一维数组,先看伪代码:

[cpp]  view plain  copy
 print ?
  1. for i = 1 ... N  
  2.     for v = 0 ... V  
  3.         f[v] = max{f[v], f[v-cost] + weight}  

你会发现,这个伪代码与01背包的伪代码只有v的循环次序不同而已。为什么这样一改就行呢?首先,想想为什么01背包问题中要按照v=V...0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰好是每种物品可选无限件,所以在考虑“加选一件dii种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][c-v[i]],所以就可以并且必须采用v=0...V的顺序循环


练习题目

题目链接: http://ac.jobdu.com/problem.php?pid=1454

代码:

[cpp]  view plain  copy
 print ?
  1. /** 
  2.  * 完全背包问题 
  3.  */  
  4.   
  5. #include   
  6. #include   
  7.   
  8. #define INF 50000000  
  9.   
  10. typedef struct coin {  
  11.     int price, weight;  
  12. } coin;  
  13.   
  14. void dynamicPackage(coin *coins, int n, int v)  
  15. {  
  16.     if (v < 0) {  
  17.         printf("This is impossible.\n");  
  18.         return;  
  19.     }  
  20.   
  21.     int i, j, *dp;  
  22.   
  23.     // 动态分配内存  
  24.     dp = (int *)malloc(sizeof(int) * (v + 1));  
  25.   
  26.     // 初始化  
  27.     dp[0] = 0;  
  28.     for (i = 1; i <= v; i ++)    dp[i] = INF;  
  29.   
  30.     // 完全背包问题  
  31.     for (i = 1; i <= n; i ++) {  
  32.         for (j = coins[i].weight; j <= v; j ++) {  
  33.             dp[j] = (dp[j] < dp[j - coins[i].weight] + coins[i].price) ? dp[j] : dp[j - coins[i].weight] + coins[i].price;  
  34.         }  
  35.     }  
  36.   
  37.     if (dp[v] >= INF)  
  38.         printf("This is impossible.\n");  
  39.     else  
  40.         printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]);  
  41.   
  42.   
  43.     // 清理内存  
  44.     free(dp);  
  45.     dp = NULL;  
  46. }  
  47.   
  48.   
  49. int main(void)  
  50. {  
  51.     int t, e, f, n, i;  
  52.     coin *coins;  
  53.   
  54.     scanf("%d", &t);  
  55.   
  56.     while (t --) {  
  57.         scanf("%d %d", &e, &f);  
  58.         scanf("%d", &n);  
  59.   
  60.         // 接收货币  
  61.         coins = (coin *)malloc(sizeof(coin) * (n + 1));  
  62.         if (coins == NULL)  exit(-1);  
  63.   
  64.         for (i = 1; i <= n; i ++) {  
  65.             scanf("%d %d", &coins[i].price, &coins[i].weight);  
  66.         }  
  67.   
  68.         // 完全背包  
  69.         dynamicPackage(coins, n, f - e);  
  70.   
  71.   
  72.         free(coins);  
  73.         coins = NULL;     
  74.     }     
  75.   
  76.     return 0;  
  77. }  


多重背包问题


题目

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大

思路

多重背包问题的思路跟完全背包的思路非常类似,只是k的取值是有限制的,因为每件物品的数量是有限制的,状态转移方程为:

[html]  view plain  copy
 print ?
  1. dp[i][v] = max{dp[i - 1][v - k * c[i]] + w[i] | 0 <=k <= n[i]}  


练习题目

题目: http://ac.jobdu.com/problem.php?pid=1455

代码:

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3.    
  4. typedef struct rice {  
  5.     int price, weight, num;  
  6. } rice;  
  7.    
  8. void dynamic(rice *rices, int m, int n)  
  9. {  
  10.     int i, j, cur, k, **dp;  
  11.    
  12.     // 动态申请二维数组  
  13.     dp = (int **)malloc(sizeof(int *) * (m + 1));  
  14.     for (i = 0; i <= m; i ++)  
  15.         dp[i] = (int *)malloc(sizeof(int) * (n + 1));  
  16.    
  17.     // 初始化  
  18.     for (i = 0; i <= m; i ++)  
  19.         for (j = 0; j <= n; j ++)  
  20.             dp[i][j] = 0;  
  21.    
  22.     // 动态规划  
  23.     for (i = 1; i <= m; i ++) {  
  24.         for (j = 1; j <= n; j ++) {  
  25.             for (k = 0; k <= rices[i].num; k ++) {  
  26.                 if (j - k * rices[i].price >= 0) {  
  27.                     cur = dp[i - 1][j - k * rices[i].price] + k * rices[i].weight;  
  28.                     dp[i][j] = dp[i][j] > cur ? dp[i][j] : cur;  
  29.                 } else {  
  30.                     break;  
  31.                 }  
  32.             }  
  33.         }  
  34.     }  
  35.    
  36.     printf("%d\n", dp[m][n]);  
  37.    
  38.     for (i = 0; i <= m; i ++)  
  39.         free(dp[i]);  
  40. }  
  41.    
  42.    
  43. int main(void)  
  44. {  
  45.     int i, c, n, m;  
  46.        
  47.     rice rices[2010];  
  48.    
  49.     scanf("%d", &c);  
  50.    
  51.     while (c --) {  
  52.         scanf("%d %d", &n, &m);  
  53.    
  54.         // 接收数据  
  55.         for (i = 1; i <= m; i ++) {  
  56.             scanf("%d %d %d", &rices[i].price, &rices[i].weight, &rices[i].num);  
  57.         }  
  58.    
  59.         // 多重背包问题  
  60.         dynamic(rices, m, n);  
  61.     }  
  62.    
  63.    
  64.     return 0;  
  65. }  
  66.    

后记

主要还是为了巩固01背包问题并且多做点题目,所以记录了一下学习《背包九讲》的过程,大家真想搞清楚背包问题,建议还是参考原文链接: http://love-oriented.com/pack/

你可能感兴趣的:(高二复习,-,2016)