背包问题
一、0-1背包问题
特点:容量为V的背包,和一些物品,物品的属性有体积w和价值v,每种物品只有一个,要求用这个背包装下尽可能多的物品,求物品的最大价值和。
- dp[i][j]表示在总体积不超过j的情况下,前i个物品所能达到的最大价值。
- dp[i][j] = max{dp[i-1][j-w]+v, dp[i-1][j]}
#include<stdio.h>
#define INF 0x7fffffff
int max (int a, int b) {
return a>b? a:b;
}
struct E {
int w;
int v;
} list[100];
int dp[101][1001];
int main() {
int s, n;
while (scanf("%d%d", &s, &n) != EOF) {
for (int i=1; i <= n; i++) {
scanf("%d%d",&list[i].w, &list[i].v);
}
for (int i=0; i<=s;i++) {
dp[0][i] = 0;
}
for (int i=1; i<=n; i++) {
for (int j=s; j>=list[i].w; j--) {
dp[i][j] = max(dp[i-1][j-list[i].w]+list[i].v, dp[i-1][j]);
}
for (int j=list[i].w-1; j>=0; j--) {
dp[i][j] = dp[i-1][j];
}
}
printf("%d\n", dp[n][s]);
}
return 0;
}
- 我们很容易发现dp[i][j]的转移仅与dp[i-1][j-list[w]]和dp[i-1][j]有关,即仅与二维数组中本行的上一行有关,根据这一特性,我们可以简化成为一维数组。
#include<stdio.h>
#define INF 0x7fffffff
int max (int a, int b) {
return a>b? a:b;
}
struct E {
int w;
int v;
} list[100];
int dp[1001];
int main() {
int s, n;
while (scanf("%d%d", &s, &n) != EOF) {
for (int i=1; i <= n; i++) {
scanf("%d%d",&list[i].w, &list[i].v);
}
for (int i=0; i<=s;i++) {
dp[i] = 0;
}
for (int i=1; i<=n; i++) {
for (int j=s; j>=list[i].w; j--) {
dp[j] = max(dp[j-list[i].w]+list[i].v, dp[j]);
}
}
printf("%d\n", dp[s]);
}
return 0;
}
二、完全背包问题
特点:每种物品的数量均为无限个。
- 我们会想我们解决0-1背包问题是采用逆序循环,保证了更新dp[j]时,dp[j-list[i].w]是没有放入物品i的数据,这是因为0-1背包中每个物品只有一个,而在完全背包中,每种物品可以被无限次选择,那么状态dp[j]可以有放入物品i的dp[j-list[i].w]转移而来,我们只需将循环变为顺序即可。
例题:Piggy-Bank
#include<stdio.h>
#define INF 0x7fffffff
int min (int a, int b) {
return a<b? a:b;
}
struct E {
int w;
int v;
} list[501];
int dp[10001];
int main() {
int T;
scanf("%d", &T);
while (T--) {
int s, tmp;
scanf("%d%d", &tmp, &s);
s -= tmp;
int n;
scanf("%d", &n);
for (int i=1; i <= n; i++) {
scanf("%d%d",&list[i].v, &list[i].w);
}
for (int i=0; i<=s;i++) {
dp[i] = INF;
}
dp[0] = 0; //恰好装满
for (int i=1; i<=n; i++) {
for (int j=list[i].w; j<=s; j++) {
if (dp[j-list[i].w] != INF) {
dp[j] = min(dp[j-list[i].w]+list[i].v, dp[j]);
}
}
}
if (dp[s] != INF) {
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[s]);
} else {
printf("This is impossible.");
}
}
return 0;
}
三、多背包问题
特点:每种物品的数量均为有限个k。
- 我们可以直接把多重背包问题直接转化到0-1背包问题,即每种物品均被视为k种不同物品,对所有的物品进行0-1背包问题。
- 为了降低最终的种类数,我们可以将原数量为k的物品拆分成若干组,每组物品看出一种,每组物品包含的原物品个数为1,2,4…。
例题:珍惜现在,感恩生活
#include<stdio.h>
#define INF 0x7fffffff
int max (int a, int b) {
return a>b? a:b;
}
struct E {
int w;
int v;
} list[2001];
int dp[101];
int main() {
int c;
scanf("%d", &c);
while (c--) {
int s, n;
scanf("%d%d", &s, &n);
int cnt = 0; //拆分后物品种类数
for (int i=1; i <= n; i++) {
int v, w, k;
scanf("%d%d%d",v,w,k);
int c = 1;
while (k-c > 0) {
k -= c;
list[++cnt].w = c*w;
list[cnt].v = c*v;
c *= 2;
}
list[++cnt].w = k*w;
list[cnt].v = k*v;
}
for (int i=0; i<=s;i++) {
dp[i] = 0;
}
for (int i=1; i<=n; i++) {
for (int j=s; j>=list[i].w; j--) {
dp[j] = max(dp[j-list[i].w]+list[i].v, dp[j]);
}
}
printf("%d\n", dp[s]);
}
return 0;
}