• 【 背包九讲——完全背包问题】


    更好的阅读体验 \color{red}{更好的阅读体验} 更好的阅读体验


    2.1 模板题


    完全背包问题

    原题链接

    描述

    有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

    第 i 种物品的体积是 vi,价值是 wi。

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
    输出最大价值。

    输入格式
    第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

    接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

    输出格式
    输出一个整数,表示最大价值。

    数据范围
    0 0

    4 5
    1 2
    2 4
    3 4
    4 5
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出样例:

    输出样例:

    10
    
    • 1

    思想

    • 状态表示:
      • 集合:dp[i][j]表示前i个物品,总体积不超过j的价值
      • 属性:最大价值
    • 状态计算:
      • 不选第i个 物品:dp[i][j] = dp[i - 1][j]
      • 选第i个物品共k个:dp[i][j] = dp[i - 1][j - k * v[i]] + k * w[i]
      • 集合属性为最大价值,故两种情况取max()
    • 当背包容量不够,即j < k * v[i]时,不能选择物品,反之可选

    代码

    #include 
    using namespace std;
    
    const int N = 1e3 +3;
    
    int dp[N][N];
    
    int v[N], w[N];
    
    void solve(){
        
        int n, m;
        
        cin >> n >> m;
        
        for(int i = 1; i <= n; i ++) cin >> v[i]  >> w[i];
        
        for(int i = 1; i <= n; i ++){
            for(int j = 0; j <= m; j ++){
                for(int k = 0; k * v[i] <= j; k ++){
                    dp[i][j] = max(dp[i][j],dp[i - 1][j - k * v[i]] + k * w[i]);
                }
            }
        }
        
        cout << dp[n][m] << endl;
        
    }
    
    int main(){
        
        solve();
        
        return 0;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    优化

    • dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i], dp[i - 1][j - 2 * v[i]] + 2 * w[i], dp[i - 1][j - 3 * v[i]] + 3 * w[i], ...)
    • dp[i][j - v[i]] = max(dp[i - 1][j - v[i]], dp[i - 1][j - 2 * v[i]] + w[i], dp[i - 1][j - 3 * v[i]] + 2 * w[i], ...)
    • dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]),与k取值无关
    • 由此k层循环可以被优化掉
    • 进一步优化,发现更新方式同01背包:
      • 01背包:dp[i][j] = max(dp[i][j],dp[i - 1][j - v[i]] + w[i])
      • 完全背包:dp[i][j] = max(dp[i][j],dp[i][j - v[i]] + w[i])
    • 即可将dp[i][j]的第一维按01背包的思想将其优化:dp[j] = max(dp[j],dp[j - v[i]] + w[i])
    • 由于更新时i不会产生歧义,故不需要逆序更新,顺序更新即可

    代码

    #include 
    using namespace std;
    
    const int N = 1e3 + 10;
    
    int dp[N];
    
    int v[N], w[N];
    
    void solve(){
        
        int n, m;
        
        cin >> n >> m;
        
        for(int i = 1; i <= n; i ++) cin >> v[i] >> w[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]] + w[i]);
            }
        }
        
        cout << dp[m] << endl;
        
    }
    
    int main(){
        
        solve();
        
        return 0;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.2 提高练习


    1023. 买书

    原题链接

    描述

    小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。

    问小明有多少种买书方案?(每种书可购买多本)

    输入格式
    一个整数 n,代表总共钱数。

    输出格式
    一个整数,代表选择方案种数。

    数据范围
    0≤n≤1000

    20
    
    • 1

    输出样例:

    输出样例:

    2
    
    • 1

    思想

    • 书的数量不限,转化为完全背包问题

    • 状态表示:

      • 集合:dp[i][j]表示前i种书,选择的价格恰好等于j的集合
      • 属性:集合的个数
    • 状态计算:

      • 不选第i种书:dp[i][j] = dp[i - 1][j]
      • 选第i种书共k本:dp[i][j] = dp[i - 1][j - k * v[i]]
      • 集合属性为集合的个数,取两种方案之和:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - k * v[i]]
    • 当背包容量不够,即j < k * v[i]时,不能选择书,反之可选

    • 优化状态计算:dp[j] += dp[j - v[i]]

    代码

    #include 
    using namespace std;
    
    const int N = 1e6 + 10;
    
    int dp[N];
    
    int v[N] = {0, 10, 20, 50, 100};
    
    void solve(){
        
        int m;
        
        cin >> m;
        
        dp[0] = 1;
        
        for(int i = 1; i <= 4; i ++){
            for(int j = v[i]; j <= m; j ++){
                dp[j] += dp[j - v[i]];
            }
        }
        
        cout << dp[m] << endl;
        
    }
    
    int main(){
        
        solve();
        
        return 0;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    1021. 货币系统 I

    原题链接

    描述

    给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。

    输入格式
    第一行,包含两个整数n和m。

    接下来n行,每行包含一个整数,表示一种货币的面值。

    输出格式
    共一行,包含一个整数,表示方案数。

    数据范围
    n≤15,m≤3000
    输入样例:

    3 10
    1
    2
    5
    
    • 1
    • 2
    • 3
    • 4

    输出样例:

    10
    
    • 1

    思想

    • 货币的数量不限,转化为完全背包问题

    • 状态表示:

      • 集合:dp[i][j]表示前i种货币,选择的面值恰好等于j的集合
      • 属性:集合的个数
    • 状态计算:

      • 不选第i种货币:dp[i][j] = dp[i - 1][j]
      • 选第i种货币共k个:dp[i][j] = dp[i - 1][j - k * v[i]]
      • 集合属性为集合的个数,取两种方案之和:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - k * v[i]]
    • 当背包容量不够,即j < k * v[i]时,不能选择货币,反之可选

    • 优化状态计算:dp[j] += dp[j - v[i]]

    代码

    #include 
    using namespace std;
    
    typedef long long LL;
    
    const LL N = 1e6 + 3;
    
    LL dp[N];
    
    LL v[N];
    
    void solve(){
        
        LL n, m;
        cin >> n >> m;
        
        dp[0] = 1;
        
        for(LL i = 1; i <= n; i ++) cin >> v[i];
        
        for(LL i = 1; i <= n; i ++){
            for(LL j = v[i]; j <= m; j ++){
                dp[j] += dp[j - v[i]];
            }
        }
        
        cout  << dp[m] << endl;
        
    }
    
    int main(){
        
        solve();
        
        return 0;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    532. 货币系统 II

    原题链接

    描述

    有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。

    为了方便,我们把货币种数为 n、面额数组为 a[1…n] 的货币系统记作 (n,a)。

    在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。

    然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。

    例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。

    两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

    现在网友们打算简化一下货币系统。

    他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。

    他们希望你来协助完成这个艰巨的任务:找到最小的 m。

    输入格式
    输入文件的第一行包含一个整数 T,表示数据的组数。

    接下来按照如下格式分别给出 T 组数据。

    每组数据的第一行包含一个正整数 n。

    接下来一行包含 n 个由空格隔开的正整数 a[i]。

    输出格式
    输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。

    数据范围
    1≤n≤100,
    1≤a[i]≤25000,
    1≤T≤20
    输入样例:

    2 
    4 
    3 19 10 6 
    5 
    11 29 13 19 17 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出样例:

    2
    5
    
    • 1
    • 2

    思想

    • n n n种货币,每种货币可以使用无穷多个,转化为完全背包
    • 对于系统 b b b,其用来表示系统 a a a的货币数量 m m m,必然满足 m ≤ n m\le n mn
    • 故我们对 a a a进行消减,将 a i a_i ai可以被其他若干 a j , a k , … , a n ( a j < a k < ⋯ < a n < a i ) a_j,a_k,\dots,a_n(a_j\lt a_k\lt\dots\lt a_n\lt a_i) aj,ak,,an(aj<ak<<an<ai)表示的 a i a_i ai消去,最终剩余的 a i a_i ai即为新的货币系统 b b b
    • 状态表示:
      • 集合:dp[i][j]表示前i个数,数值为j的方案
      • 属性:该方案是否出现过
    • 状态计算:
      • vis[N]记录出现过的方案数,cnt记录选择的最小数量
      • dp[i,vis[i]] == 0说明该方案未曾出现,则cnt ++
      • 不选择该方案:dp[i][j] = dp[i - 1][j]
      • 选则该方案:dp[i][j] = dp[i][j - vis[i]]
      • 集合属性为该方案是否出现过,取两种方案之和:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - vis[i]]
      • 优化:dp[j] += dp[j - vis[i]]

    代码

    #include 
    using namespace std;
    
    const int N = 1e6 + 3;
    
    int vis[N];
    
    bool dp[N];
    
    void solve(){
        
        memset(dp, 0, sizeof dp);
        
        int n;
        
        cin >> n;
        
        for(int i = 0; i < n; i ++) cin >> vis[i];
        
        sort(vis, vis + n);
        
        int maxn = vis[n - 1];
        
        int cnt = 0;
        
        dp[0] = 1;
        
        for(int i = 0; i < n; i ++){
            if(!dp[vis[i]]) cnt ++;
            
            for(int j = vis[i]; j <= maxn; j ++){
                dp[j] += dp[j - vis[i]];
            }
        }
        
        cout << cnt << endl;
        
    }
    
    int main(){
        
        int _;
        
        cin >> _;
        
        while(_ --){
            solve();
        }
        
        return 0;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

  • 相关阅读:
    macOS Monterey12.5.1 配置vim
    语义分割之RTFormer介绍
    PMI 为什么不公布 PMP 题目和 PMP 考试答案
    易语言 如何调用麦谈帮API接口?
    一个 SpringBoot 问题就干趴下了?我却凭着这份 PDF 文档吊打面试官
    浅谈双十一背后的支付宝LDC架构和其CAP分析
    springcloud+nacos+dubbo服务器部署问题
    构建LangChain应用程序的示例代码:35、如何使用假设性文档嵌入(HyDE)技术来改善文档索引教程
    JVM之【执行引擎】
    【SMTP协议】关于SMTP AUTH命令导致鉴权
  • 原文地址:https://blog.csdn.net/LYS00Q/article/details/126171546