背包问题

01背包问题

二维表示

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN][MAXN];
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)     scanf("%d %d
", &v[i], &val[i]);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++){
            //当前背包容量j装不下第i个物品
            if(j < v[i])
                dp[i][j] = dp[i - 1][j];
            //可以装下第i个物品了,选出装或不装的最大值
            //dp[i - 1][j - v[i]] + val[i]的意思是:由于当前的dp数组要依靠上
            //一层的dp[i - 1][...],所以间接表示装下第i个物品后背包的最大价值
            else
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + val[i]);
        }
    cout << dp[n][m];
    return 0;
}

一维表示

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN];
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)     scanf("%d %d
", &v[i], &val[i]);
    for(int i = 1; i <= n; i++)
        for(int j = m; j >= 1; j--)
            //删除第一个变量i后,dp[j] = dp[j],忽略
            //dp[i][j] = dp[i - 1][j];
            if(j >= v[i])
                dp[j] = max(dp[j], dp[j - v[i]] + val[i]);
    cout << dp[m];
    return 0;
}

当二维优化成一维时,背包容量j从大到小的原因是:f[i, j]要用f[i - 1, j - v[i]] + w[i]来更新,从大到小可以保证算f[j]时用到的f[j - v[i]]存储的是f[i - 1, j - v[i]],而不是f[i, j - v[i]];如果从小到大循环,那么f[j - v[i]]会在f[j]前被计算出来,那么它就表示f[i, j - v[i]]了。

由此可以得出:背包问题中若用到上一层的状态时从大到小遍历,若用到本层的状态时从小到大遍历。

例题:子集背包问题

原问题链接:416. 分割等和子集

二维表示

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int a : nums)   sum += a;
        if(sum % 2 != 0)    return false;
        int n = nums.size();
        sum /= 2;
        //dp[i][j]表示前i个数组成的子集之和是否为j
        vector<vector<bool>> dp(n + 1, vector<bool>(sum + 1, false));
        for(int i = 0; i <= n; i++)     dp[i][0] = true;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= sum; j++){
                if(j < nums[i - 1])
                    dp[i][j] = dp[i - 1][j];
                else
                    dp[i][j] = dp[i - 1][j - nums[i - 1]] || dp[i - 1][j];
            }
        return dp[nums.size()][sum];
    }
};

一维表示

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int a : nums)   sum += a;
        if(sum % 2 != 0)    return false;
        int n = nums.size();
        sum /= 2;
        vector<bool> dp(sum + 1, false);
        dp[0] = true;
        for(int i = 1; i <= n; i++)
            for(int j = sum; j >= 1; j--)
                if(j >= nums[i - 1])
                    dp[j] = dp[j - nums[i - 1]] || dp[j];
        return dp[sum];
    }
};

完全背包问题

常规背包问题

二维表示(三层for循环)

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN][MAXN];
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        scanf("%d %d
", &v[i], &val[i]);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            for(int k = 0; k * v[i] <= j; k++)
                //这里的k = 0时,表示背包不选第i的物品的情况,此时dp[i][j] = dp[i - 1][j]
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * val[i]);
    cout << dp[n][m] << endl;
    return 0;
}

二维表示(二层for循环)

f[i , j] = max( f[i - 1, j] , f[i - 1, j - v[i]] + w[i], f[i-1,j-2 * v[i]] + 2 * w[i], f[i - 1, j - 3 * v[i]]+3 * w[i] ...)
f[i , j - v[i]]= max( f[i - 1, j - v[i]], f[i-1,j-2 * v[i]] + w[i], f[i - 1, j - 2 * v[i]]+2 * w[i] ...)

当前背包容量 j 可以装下第 i 个物品时,则:f [ i ] [ j ] = max( f [ i - 1 ] [ j ] , f[ i ] [ j - v [ i ]] + w[i]),不断使用前面已经更新过的 f [ i ] [ ... < j ]来更新 f [ i ] [ j ],就像之前所说的,这里f [ i ] [ j ]用到了f [ i ] [ ...< j ],尚在同一层,所以从小到大遍历 j

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN][MAXN];
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        scanf("%d %d
", &v[i], &val[i]);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++){
            if(j < v[i])
                dp[i][j] = dp[i - 1][j];
            else
                dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + val[i]);
        }
    cout << dp[n][m] << endl;
    return 0;
}

一维表示

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int v[N], val[N], dp[N];
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        scanf("%d %d
", &v[i], &val[i]);
    for(int i = 1; i <= n; i++)
        for(int j = v[i]; j <= m; j++)
            dp[j] = max(dp[j], dp[j - v[i]] + val[i]);
    cout << dp[m] << endl;
    return 0;
}

例题:零钱兑换问题

原问题链接:518. 零钱兑换 Ⅱ

二维表示

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        //dp[i][j]表示前i种硬币凑成总金额j的硬币组合数
        int n = coins.size();
       vector<vector<int>> dp(n + 1, vector<int>(amount + 1));
        for(int i = 0; i <= n; i++)  dp[i][0] = 1;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= amount; j++)
                if(j < coins[i - 1])
                    dp[i][j] = dp[i - 1][j];
                else
                    dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
        return dp[n][amount];
    }
};

一维表示

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        //dp[i][j]表示前i种硬币凑成总金额j的硬币组合数
        int n = coins.size();
        vector<int> dp(amount + 1);
        dp[0] = 1;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= amount; j++)
                if(j >= coins[i - 1])
                    dp[j] = dp[j] + dp[j - coins[i - 1]];
        return dp[amount];
    }
};

多重背包问题

二维表示

可以看出和完全背包问题相差无几...

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 110;
int v[MAXN], val[MAXN], s[MAXN], dp[MAXN][MAXN];
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        scanf("%d %d %d
", &v[i], &val[i], &s[i]);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * val[i]);
    cout << dp[n][m];
    return 0;
}

一维表示

由于多重背包问题的状态涉及第 i 个物品的数量 s [ i ], 则 k 不能省略

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 110;
int v[MAXN], val[MAXN], s[MAXN], dp[MAXN];
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        scanf("%d %d %d
", &v[i], &val[i], &s[i]);
    for(int i = 1; i <= n; i++)
        for(int j = m; j >= 1; j--)
            for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
                dp[j] = max(dp[j], dp[j - k * v[i]] + k * val[i]);
    cout << dp[m];
    return 0;
}
原文地址:https://www.cnblogs.com/i-chase/p/14106916.html