• 01背包


    问题描述

    有种n种物品,每种只有一个,第i种物体的体积为w[i],价值为v[i].选一些物品装到一个容量为C的背包,使得背包内物体体积不超过C的前提下价值尽量大.

    分析

    首先约定Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积;定义dp(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值
    递推过程:

    • 若物品不能装下,则此时的价值与上一个阶段相同,即dp[i][j] = dp[i-1][j];
    • 若物品能够装下,但是装下不一定最优,则此时的价值应与i-1作比较,即dp[i][j] =max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);

    模板题 [http://acm.hdu.edu.cn/showproblem.php?pid=2602]

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long
    
    ll v[1005], w[1005];
    ll dp[1005][1005]; // dp[i][j]表示当前背包容量 j,前 i 个物品最佳组合对应的价值
    
    int main() {
        ll n, c, t;
        cin >> t;
        while (t--) {
            cin >> n >> c;
            for(ll i = 1; i <= n; i++) scanf("%lld", v+i);
            for(ll i = 1; i <= n; i++) scanf("%lld", w+i);
            memset(dp, 0, sizeof dp);
            for(ll i = 1; i <= n; i++) { // 从第一个物体到第n个物体
                for(ll j = 0; j <= c; j++) { // 背包容量
                    if(j >= w[i]) { // 背包能放得开
                        dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
                        // 前者大就是不放第i个东西价值大,后者就是放第i个东西价值大
                    }     
                    else// 放不开则等于上一阶段的价值
                        dp[i][j] = dp[i-1][j]; 
                }
            }
            cout << dp[n][c] << endl;
        }
        
        return 0;
    }
    

    拓展

    • 输出路径:
      放入背包的输出1,否则输出0
    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long
    
    ll v[1005], w[1005];
    ll dp[1005][1005], path[1005][1005]; 
    
    int main() {
        ll n, c, t;
        cin >> t;
        while (t--) {
            cin >> n >> c;
            for(ll i = 1; i <= n; i++) scanf("%lld", v+i);
            for(ll i = 1; i <= n; i++) scanf("%lld", w+i);
            memset(dp, 0, sizeof dp);
            for(ll i = 1; i <= n; i++) {
                for(ll j = 0; j <= c; j++) {
                    if(j >= w[i]) {
                        if(dp[i-1][j] < dp[i-1][j-w[i]]+v[i]) {
                            dp[i][j] = dp[i-1][j-w[i]]+v[i];
                            path[i][j] = 1;
                        }
                        else dp[i][j] = dp[i-1][j];
                    } // 可以放东西     
                    else 
                        dp[i][j] = dp[i-1][j];
                }
            }
            cout << dp[n][c] << endl;
        }
        for (ll i = 1,j = c;i <= n&&j > 0;i++){
            if (path[i][j]) {
                printf("1 ");
                j -= w[i];
    		} else printf("0 ");
        }
        return 0;
    }
    
    
    • 一维数组优化:
      用dp[j]表示第i次循环后,前i个物体放到容量j时的最大价值,但是和二维数组不同,第i次循环会可能覆盖第i-1次循环的结果.而且,背包容量j必须逆序枚举.
      逆序枚举容量的原因:
      注意一点,我们是由第 i - 1 次循环的两个状态推出 第 i 个状态的,而且 w > w- v[i],则对于第i次循环,背包容量只有当c..0循环时,才会先处理背包容量为v的状况,后处理背包容量为 w-v[i] 的情况。

      具体来说,由于,在执行v时,还没执行到w- v[i]的, 因此,dp[j - w[i]]保存的还是第i - 1次循环的结果。即在执行第i次循环 且 背包容 量为v时,此时的dp[j]存储的是 dp[j - 1][j] ,此时dp[j-w[i]]存储的 是dp[i - 1][j-w[i]]。

      相反,如果在执行第 i 次循环时,背包容量按照0..c的顺序遍历一 遍,来检测第 i 件物品是否能放。此时在执行第i次循环 且 背包容量为v时,此时的dp[j]存储的是 dp[i - 1][v] ,但是,此时dp[j-w[i]]存储的是dp[i][j-w[i]]。

      因为,w > w- v[i],第i次循环中,执行背包容量为j时,容量为w > w- v[i]的背包已经计算过,即dp[j-w[i]]中存储的是dp[j-w[i]]。即,对于01背包,按照增序枚举背包容量是不对的。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long
    
    ll v[1005], w[1005];
    ll dp[1005]; // 表示第i次循环后,前i个物体放到容量j时的最大价值
    
    int main() {
        ll n, c, t;
        cin >> t;
        while (t--) {
            cin >> n >> c;
            for(ll i = 1; i <= n; i++) scanf("%lld", v+i);
            for(ll i = 1; i <= n; i++) scanf("%lld", w+i);
            memset(dp, 0, sizeof dp);
            for(ll i = 1; i <= n; i++) { // 从第一个物体到第n个物体
                for(ll j = c; j >= 0; j--) { // 注意此时背包容量要逆序枚举
                    if(j >= w[i]) { // 背包能放得开
                        dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
                        // 前者大就是不放第i个东西价值大,后者就是放第i个东西价值大
                    }      
                    // 第i次循环开始时dp[j]里存储的是第i-1次循环的结果
                }
            }
            cout << dp[c] << endl;
        }
        
        return 0;
    }
    

    参考博文 https://blog.csdn.net/qq_37767455/article/details/99086678
    https://blog.csdn.net/tomorrowtodie/article/details/51090715
    https://blog.csdn.net/liusuangeng/article/details/38374405

  • 相关阅读:
    (AIDE)Android Eclipse JNI 调用 .so文件加载问题
    VMware10.06精简版安装后台运行
    RAID磁盘阵列笔记
    高科固定电话机铃声
    嵌入式Linux驱动学习之路(十八)LCD驱动
    嵌入式Linux驱动学习之路(十七)驱动程序分层分离概念-平台设备驱动
    嵌入式Linux驱动学习之路(十六)输入子系统
    嵌入式Linux驱动学习之路(十五)按键驱动-定时器防抖
    嵌入式Linux驱动学习之路(十四)按键驱动-同步、互斥、阻塞
    嵌入式Linux驱动学习之路(十三)按键驱动-异步通知
  • 原文地址:https://www.cnblogs.com/knightoflake/p/14529602.html
Copyright © 2020-2023  润新知