您的位置:首页 > 其它

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

2013-10-04 15:03 302 查看

前言

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

01背包

题目

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

思路

这是最基础的背包问题,特点是:每种物品只有一件,可以选择放或者不放
用子问题定义状态:即dp[i][j]表示前i件物品放入一个容量为j的背包可以获得的最大价值。则其状态转移方程为:
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]]的值。伪代码如下:
for i  in 0 ... N
for  v = V ... 0
f[v] = max{f[v], f[v-c[i]] + w[i]}


练习题目

题目:http://acm.hdu.edu.cn/showproblem.php?pid=2602
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N 1010

int value
, volume
, dp
;

// 0-1背包,优化空间
void dpPackage(int n, int v)
{
int i, j;

memset(dp, 0, sizeof(dp));

for (i = 1; i <= n; i ++) {
for (j = v; j >= volume[i]; j --) {
dp[j] = dp[j] > dp[j - volume[i]] + value[i] ? dp[j] : dp[j - volume[i]] + value[i];
}
}

printf("%d\n", dp[v]);
}

int main(void)
{
int i, t, n, v;

scanf("%d", &t);

while (t --) {
// 接收参数
scanf("%d %d", &n, &v);

for (i = 1; i <= n; i ++)	scanf("%d", value + i);
for (i = 1; i <= n; i ++)	scanf("%d", volume + i);

// 0-1背包
dpPackage(n, v);
}

return 0;
}


完全背包问题

题目

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

思路

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

这个算法使用一维数组,先看伪代码:
for i = 1 ... N
for v = 0 ... V
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
代码:
/**
* 完全背包问题
*/

#include <stdio.h>
#include <stdlib.h>

#define INF 50000000

typedef struct coin {
int price, weight;
} coin;

void dynamicPackage(coin *coins, int n, int v)
{
if (v < 0) {
printf("This is impossible.\n");
return;
}

int i, j, *dp;

// 动态分配内存
dp = (int *)malloc(sizeof(int) * (v + 1));

// 初始化
dp[0] = 0;
for (i = 1; i <= v; i ++)	dp[i] = INF;

// 完全背包问题
for (i = 1; i <= n; i ++) {
for (j = coins[i].weight; j <= v; j ++) {
dp[j] = (dp[j] < dp[j - coins[i].weight] + coins[i].price) ? dp[j] : dp[j - coins[i].weight] + coins[i].price;
}
}

if (dp[v] >= INF)
printf("This is impossible.\n");
else
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]);

// 清理内存
free(dp);
dp = NULL;
}

int main(void)
{
int t, e, f, n, i;
coin *coins;

scanf("%d", &t);

while (t --) {
scanf("%d %d", &e, &f);
scanf("%d", &n);

// 接收货币
coins = (coin *)malloc(sizeof(coin) * (n + 1));
if (coins == NULL)	exit(-1);

for (i = 1; i <= n; i ++) {
scanf("%d %d", &coins[i].price, &coins[i].weight);
}

// 完全背包
dynamicPackage(coins, n, f - e);

free(coins);
coins = NULL;
}

return 0;
}


多重背包问题

题目

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

思路

多重背包问题的思路跟完全背包的思路非常类似,只是k的取值是有限制的,因为每件物品的数量是有限制的,状态转移方程为:
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
代码:
#include <stdio.h>
#include <stdlib.h>

typedef struct rice {
int price, weight, num;
} rice;

void dynamic(rice *rices, int m, int n)
{
int i, j, cur, k, **dp;

// 动态申请二维数组
dp = (int **)malloc(sizeof(int *) * (m + 1));
for (i = 0; i <= m; i ++)
dp[i] = (int *)malloc(sizeof(int) * (n + 1));

// 初始化
for (i = 0; i <= m; i ++)
for (j = 0; j <= n; j ++)
dp[i][j] = 0;

// 动态规划
for (i = 1; i <= m; i ++) {
for (j = 1; j <= n; j ++) {
for (k = 0; k <= rices[i].num; k ++) {
if (j - k * rices[i].price >= 0) {
cur = dp[i - 1][j - k * rices[i].price] + k * rices[i].weight;
dp[i][j] = dp[i][j] > cur ? dp[i][j] : cur;
} else {
break;
}
}
}
}

printf("%d\n", dp[m]
);

for (i = 0; i <= m; i ++)
free(dp[i]);
}

int main(void)
{
int i, c, n, m;

rice rices[2010];

scanf("%d", &c);

while (c --) {
scanf("%d %d", &n, &m);

// 接收数据
for (i = 1; i <= m; i ++) {
scanf("%d %d %d", &rices[i].price, &rices[i].weight, &rices[i].num);
}

// 多重背包问题
dynamic(rices, m, n);
}

return 0;
}

后记

主要还是为了巩固01背包问题并且多做点题目,所以记录了一下学习《背包九讲》的过程,大家真想搞清楚背包问题,建议还是参考原文链接:http://love-oriented.com/pack/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: