背包问题

背包问题

一、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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值