背包问题

0-1背包

有一堆物品,每个物品都有其重量和价值,现在有一个只能容纳10kg物品的背包,选择装入一些物品,使得背包中的物品价值最大

image-20220210094225320

  • DPi,j表示前i个物品,装进容量为j的背包所获得的最大价值
  • w[i]为第i件物品的重量
  • v[i]为第i件物品的价值

朴素递归

时间复杂度O(2n)

#include<stdio.h>
#include<iostream>
using namespace std;

const int MAXN = 1000 + 10;
int weight[MAXN];
int value[MAXN];

int Fun1(int n, int c){
    int answer;
    if(n == 0 || c == 0){
        answer = 0;
    }
    else{
        if(c < weight[n]){
            answer = Fun1(n-1, c);
        }
        else {
            answer = max(Fun1(n - 1, c), Fun1(n - 1, c - weight[n]) + value[n]);
        }
    }
    return answer;
}

int main(){
    int c, n;
    while(scanf("%d%d", &c, &n) != EOF){
        for(int i = 1; i <= n; ++i){
            scanf("%d%d", &weight[i], &value[i]);
        }
        printf("%d\n", Fun1(n, c));

    }
    return 0;
}

递归策略+记忆化

时间复杂度O(n*c)

#include<stdio.h>
#include<iostream>
using namespace std;

const int MAXN = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int memo[MAXN][MAXN];   //记录,初始化均为-1,表示该子问题未解决

int Fun1(int n, int c){
    if(memo[n][c] != -1){   //该子问题已经解决,直接查表
        return memo[n][c];
    }
    int answer;
    if(n == 0 || c == 0){
        answer = 0;
    }
    else{
        if(c < weight[n]){
            answer = Fun1(n-1, c);
        }
        else {
            answer = max(Fun1(n - 1, c), Fun1(n - 1, c - weight[n]) + value[n]);
        }
    }
    memo[n][c] = answer;    //记录子问题的解
    return answer;
}

int main(){
    int c, n;
    while(scanf("%d%d", &c, &n) != EOF){
        for(int i = 1; i <= n; ++i){
            scanf("%d%d", &weight[i], &value[i]);
        }
        for(int i = 0; i <= n; ++i){
            for(int j = 0; j <= c; ++j){
                memo[i][j] = -1;
            }
        }
        printf("%d\n", Fun1(n, c));

    }
    return 0;
}

递推求解

时间复杂度O(n*c)

空间复杂度O(n*c)

#include<stdio.h>
#include<iostream>
using namespace std;

const int MAXN = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXN];


int main(){
    int c, n;
    while(scanf("%d%d", &c, &n) != EOF){
        for(int i = 1; i <= n; ++i){
            scanf("%d%d", &weight[i], &value[i]);
        }

        //初始化dp(相当于递归出口)
        for(int i = 0; i <= n; ++i){    //背包空间为0
            dp[i][0] = 0;
        }
        for(int j = 0; j <= c; ++j){    //选取物品数为0
            dp[0][j] = 0;
        }

        for(int i = 1; i <= n; ++i){
            for(int j = 1; j <= c; ++j){
                if(j < weight[i]){
                    dp[i][j] = dp[i-1][j];
                }
                else{
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
                }
            }
        }
        printf("%d\n", dp[n][c]);
    }
    return 0;
}

空间优化

由于求解第i个物品是否放入背包时,只需要用到第i-1个物品的状态,因此可以将二位数组压缩为一维数组,只需要记录第i-1件物品的状态,求第i件物品状态的时候反向求解即可(正向求解会提前覆盖需要使用的数据)

时间复杂度O(n*c)

空间复杂度O(c)

#include<stdio.h>
#include<iostream>
using namespace std;

const int MAXC = 1000 + 10;
int weight[MAXC];
int value[MAXC];
int dp[MAXC];   //改为一维数组,记录第i-1件物品的状态


int main(){
    int c, n;
    while(scanf("%d%d", &c, &n) != EOF){
        for(int i = 1; i <= n; ++i){
            scanf("%d%d", &weight[i], &value[i]);
        }

        for(int j = 0; j <= c; ++j){    //初始化
            dp[j] = 0;
        }

        for(int i = 1; i <= n; ++i){
            for(int j = c; j >= weight[i]; --j){    //对第i件物品逆向求解
                //对于j<weight[i]的情况不用更新
                dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
            }
        }
        printf("%d\n", dp[c]);
    }
    return 0;
}

完全背包

完全背包和0-1背包(背包中某件物品的数量要么是0要么是1)的区别在于,同一件物品可以有无穷多件,这一点体现在递推公式中为第i件物品放入背包后仍可以继续选择第i件物品

image-20220210180809935

递推求解

#include<stdio.h>
#include<iostream>
using namespace std;

const int MAXN = 100 + 10;
const int MAXM = 1E5 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXM];


int main(){
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF){
        for(int i = 1; i <= n; ++i){
            scanf("%d%d", &value[i], &weight[i]);
        }
        scanf("%d", &m);

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

        for(int i = 1; i <= n; ++i){
            for(int j = 1; j <= m; ++j){
                if(j < weight[i]){
                    dp[i][j] = dp[i-1][j];
                }
                else{
                    dp[i][j] = max(dp[i-1][j], dp[i][j-weight[i]] + value[i]);
                }
            }
        }
        printf("%d\n", dp[n][m]);
    }
    return 0;
}

空间优化

时间复杂度O(n*m)

空间复杂度O(n*m)

完全背包在求解第i件物品的状态时只需要用到第i-1件物品的状态和第i件物品的状态,因此可将二维数组压缩为一维数组,只记录第i-1个物品的状态,在求解第i件物品的状态时更新即可

完全背包空间优化和0-1背包空间优化的区别在于,0-1背包在求解第i件物品的状态时是反向更新,要保证第i-1件物品的状态不变。而完全背包在求解第i件物品的状态时,需要更新后的第i-1件物品的状态,因此采用正向更新

#include<stdio.h>
#include<iostream>
using namespace std;

const int MAXN = 100 + 10;
const int MAXM = 1E5 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXM];   //改为一维数组,记录第i-1行状态


int main(){
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF){
        for(int i = 1; i <= n; ++i){
            scanf("%d%d", &value[i], &weight[i]);
        }
        scanf("%d", &m);

        //初始化
        for(int j = 0; j <= m; ++j){
            dp[j] = 0;
        }

        for(int i = 1; i <= n; ++i){
            for(int j = weight[i]; j <= m; ++j){    //正向更新
                //j < weight[i]时不用更新
                dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
            }
        }
        printf("%d\n", dp[m]);
    }
    return 0;
}

多重背包

多重背包问题介于0-1背包和完全背包问题之间,即每件物品既不是只有0-1件,也不是有无穷多见,而是有k件,问此时如何选择使得装入背包物品的总价值最大。

思路1:将多重背包问题转为0-1背包问题,即将k件物品看作价值相同且重量相同的k件不同物品

思路2:先组合,将k件相同的物品组合成1件物品,其价值和重量均为原来的k倍,再作为0-1背包问题求解。但不能任意进行组合,组合完成后需要能够满足任意需求的这件物品,则组合策略可以为

image-20220211073650832

举个例子,k=5,按上述组合策略,5=1+2+2,可满足任意需求的物品数

1=1,2=2,3=1+2,4=2+2,5=1+2+2

#include<stdio.h>
#include<iostream>
using namespace std;

const int MAXN = 100 + 10;
const int MAXM = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int amount[MAXN];           //每件物品的数量
int dp[MAXM];
int newWeight[20 * MAXN];   //组合后每件物品的重量
int newValue[20 * MAXN];    //组合后每件物品的价值


int main(){
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF){
        int number = 0;     //组合后物品的数量
        for(int i = 1; i <= n; ++i){
            scanf("%d%d%d", &value[i], &weight[i], &amount[i]);
            //按照组合策略进行组合
            for(int j = 1; i <= amount[i]; j *= 2){     //组合2^n
                ++number;
                newWeight[i] = j * weight[i];
                newValue[i] = j * value[i];
                amount[i] -= j;
            }
            if(amount[i] > 0){  //组合剩余的
                ++number;
                newWeight[i] = amount[i] * weight[i];
                newValue[i] = amount[i] * value[i];
            }
        }
        
        //一下调用0-1背包问题:递推求解+空间优化
        
        //初始化
        for(int j = 0; j <= m; ++j){
            dp[j] = 0;
        }

        for(int i = 1; i <= number; ++i){   //number为组合后物品数量
            for(int j = m; j >= newWeight[i]; --j){     //newWeight为组合后物品重量
                dp[j] = max(dp[j], dp[j-newWeight[i]] + newValue[i]);   //newValue为组合后物品价值
            }
        }
        printf("%d\n", dp[m]);
    }
    return 0;
}
posted @ 2022-02-11 08:00  dctwan  阅读(26)  评论(0编辑  收藏  举报