【C++题解】动态规划-背包问题

核心是吃透DP中的重要分支——背包问题。

昨天发的那个版本排版不行,所以今天重新发一个,然后把01背包题解配上了

下午 (4小时): 动态规划专题——背包问题

背包问题是动态规划中的一个庞大家族,它通过“选择”与“不选择”的决策来求解在有限“容量”下的最优解。掌握背包模型,不仅能解决模板题,更能帮助您识别出许多实际问题背后的DP本质。

练习计划概览

  • 总时长: 约 4 小时

  • 核心目标:

    1. 掌握 0-1背包 的状态定义、状态转移方程以及空间优化(滚动数组)的实现。

    2. 掌握 完全背包 的模型,并深刻理解其与0-1背包在空间优化实现上的核心差异(循环顺序)。

    3. 学习识别背包问题的变种,将实际问题抽象为背包模型。

    4. 通过CCF真题,感受背包问题在实际竞赛中的应用。


第一部分:0-1 背包 —— 每种物品仅此一件 (约 1.5 小时)

这是最基础的背包模型:有N件物品和一个容量为V的背包,每件物品有自己的重量(或成本)和价值,每件物品最多只能选择一次。求解将哪些物品装入背包,可使这些物品的价值总和最大。

题目编号题目名称核心知识点练习目标
Luogu P1048[NOIP2005 普及组] 采药0-1背包模板DP入门必做。这是最纯粹的0-1背包问题。请务必亲手实现二维 dp[i][j] 版本以理解原理,然后掌握一维 dp[j] 的空间优化写法,并理解为何容量 j 必须**从大到小(逆序)**遍历。
CCF202209-2何以包邮?0-1背包变种将0-1背包思想应用于不同的求解目标。问题是在满足总价不低于x 的前提下求最小花费。可以定义 dp[j] 为“能否凑成总价为 j”,也可以定义为“凑成总价 j 的最小件数/最大价值”,是一种典型的变种练习。(来自您提供的文件) 1
题解
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
    int time,n;
    cin >> time >> n;
    vector<pair<int,int>> v(n);
    vector<vector<int>> dp(n,vector<int>(time+1,0));
    for(int i=0;i<n;i++){
        cin >> v[i].first >> v[i].second;
    }
    // 初始化第一行:只考虑第一个物品
    for(int j=0;j<=time;j++){
        if(j>=v[0].first){
            dp[0][j] = v[0].second;
        }
    }
    // 填充dp表,从第二个物品开始
    for(int i=1;i<n;i++){
        for(int j=0;j<=time;j++){
            if(j>=v[i].first){
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i].first]+v[i].second);
            }else{
                dp[i][j] = dp[i-1][j];
            }
        }
    }
    cout << dp[n-1][time] << endl;
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>

using namespace std;

int main(){
    int n, m;
    cin >> n >> m;

    // 存储每本书的价格
    vector<int> price(n);
    int totalSum = 0;
    for(int i = 0; i < n; i++){
        cin >> price[i];
        totalSum += price[i];
    }

    // 如果所有书的总价都小于m,无法满足包邮条件
    if(totalSum < m){
        cout << 0 << endl;
        return 0;
    }

    // dp[i][j] 表示前i本书能否组成价格为j的组合
    // 由于价格可能很大,我们需要限制dp数组的大小
    // 最大价格不会超过所有书价格的总和
    vector<vector<bool>> dp(n + 1, vector<bool>(totalSum + 1, false));

    // 初始化:不选任何书,价格为0
    dp[0][0] = true;

    // 动态规划
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= totalSum; j++){
            // 不选第i本书
            dp[i][j] = dp[i-1][j];

            // 选第i本书(如果价格允许)
            if(j >= price[i-1]){
                dp[i][j] = dp[i][j] || dp[i-1][j - price[i-1]];
            }
        }
    }

    // 找到满足条件(>=m)的最小价格
    int minCost = INT_MAX;
    for(int j = m; j <= totalSum; j++){
        if(dp[n][j]){
            minCost = j;
            break;
        }
    }

    if(minCost == INT_MAX){
        cout << 0 << endl;
    } else {
        cout << minCost << endl;
    }

    return 0;
}

练习建议:

  • 理解核心状态dp[j] 的含义是“在容量限制为 j 的情况下,所能获得的最大价值”。

  • 理解逆序遍历:在一维优化中,对容量 j 的逆序遍历是为了保证在计算 dp[j] 时,所依赖的 dp[j - weight[i]] 还是上一轮(即不包含物品 i)的状态。这样就保证了每个物品 i 最多只被放入一次。


第二部分:完全背包 —— 物品无限供应 (约 2 小时)

完全背包与0-1背包唯一的区别在于:每种物品可以被选择无限次。这个小小的改动,在代码实现上带来了奇妙的变化。

题目编号题目名称核心知识点练习目标
Luogu P1616疯狂的采药完全背包模板完全背包的模板题。请在一维空间优化的实现中,对比它与0-1背包的代码。你会发现,只需将容量 j 的遍历顺序改为**从小到大(正序)**即可。思考并理解为什么这样做是正确的。
CCF202406-4货物调度分组背包, 0-1背包这是一个非常好的进阶题。问题可以转化为分组背包模型:每个仓库是一个“物品组”,组内有多个“物品”(即选择从该仓库卖1件、2件、3件…货物)。解决这个问题需要您对背包DP有更深层次的理解。

练习建议:

  • 理解正序遍历:在完全背包的一维优化中,对容量 j 的正序遍历,使得我们在计算 dp[j] 时,所依赖的 dp[j - weight[i]] 可能已经是本轮(即已考虑物品 i)更新过的状态。这相当于允许物品 i 被重复选择,从而正确地实现了完全背包的逻辑。

  • 化归思想:在做 货物调度 时,尝试将问题分解。对于每个仓库,先预处理出“从这个仓库卖出k件商品能获得的最大净利润”,然后问题就转化为了“有N个仓库(物品组),每个仓库有多种选择方案,求最大总利润”的分组背包问题。


目标达成自查 (约 0.5 小时)

完成以上练习后,进行复盘和总结:

  1. 0-1背包 vs 完全背包:

    • 两种背包问题的核心区别是什么?(物品能否重复选取)。

    • 这个区别如何体现在一维空间优化的代码上?(0-1背包容量逆序遍历,完全背包容量正序遍历)。

  2. 如何识别背包问题?

    • 问题是否可以描述为:从一堆“物品”中进行选择,放入一个有“容量”限制的“背包”里,以期达到“价值”最优?

    • “重量/成本”、“价值”、“容量”这些概念在题目中可能是以什么形式出现的?(例如“时间”、“花费”、“价格”、“满意度”、“资源限制”等)。

  3. 背包问题的本质:

    • 背包DP的本质是一种“选择”的决策过程。对于每个物品,决策是“选”还是“不选”(0-1背包),或者是“选0件、1件、2件…”(完全背包/多重背包)。DP的过程就是记录下每个决策阶段的最优状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值