• 背包问题


    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;
    }
    
  • 相关阅读:
    绘画字符串
    方法名,类名与字符串之间的转换
    重写视图的点击区域
    ios7新特性
    常用的sq语句
    基于xmpp的ios聊天客户端(一)
    iOS真机测试,为Provisioning添加设备
    【IOS 开发】Object-C 运算符
    【Android 应用开发】 自定义组件 宽高适配方法, 手势监听器操作组件, 回调接口维护策略, 绘制方法分析 -- 基于 WheelView 组件分析自定义组件
    博客技术资料整理
  • 原文地址:https://www.cnblogs.com/i-chase/p/14106916.html
Copyright © 2020-2023  润新知