动态规划----01背包问题

点击查看代码
/*
1.01背包问题
题目:https://ac.nowcoder.com/acm/problem/226514
你有⼀个背包,最多能容纳的体积是 V 。
现在有 n 个物品,第 i 个物品的体积为 vi ,价值为 wi 。
1. 求这个背包⾄多能装多⼤价值的物品?
2. 若背包恰好装满,求⾄多能装多⼤价值的物品?

2.讲解算法原理:
        解法一:贪心
            1.尽可能的选择价值较大的物品
            2,尽可能的选择体积较小的物品
            3.尽可能的选择 w/v 较大的物品
            ......
            都不行,有反例

        解法二:动态规划
        第一问:
            1.状态表示
                f[i] 表示: 在[1, i]区间内 挑选物品, 所有的选法下, 最大的价值---不行
                f[i][j] 表示: 在[1, i]区间内挑选物品, 总体积不能超过j, 所有的选法下, 最大价值

                第二问:
                f[i][j] 表示: 在[1, i]区间内挑选物品, 总体积必须等于j, 所有的选法下, 最大价值

            2.状态转移方程
                            1.不选i ---->  f[i-1][j]
                f[i]
                            2.选i   ---->  w[i] + f[i-1][j - v[i]]   (j >= v[i])
            
            3.初始化
                第一问:f[i][j]  i == 0 行初始化为0

                第二问:
                    第一种解决方式:
                        非法的各自初始化为-1,在状态转移方程的时候,特判一下即可
                    第二种解决方式:(更好)
                        非法的格子全部初始化为负无穷,仅需在最终结果的位置判断一下即可

            4.填表顺序  从上到下每一行

            5.最终结果
                    f[n][v]

            6.空间优化
                1.考虑是否修改遍历顺序
                2.直接在原始的代码上,删掉第一维即可
            
*/


#include <iostream>
// memset函数需要的头文件(用来初始化数组)
#include <cstring>
// max函数需要的头文件(部分编译器不包含会报错)
#include <algorithm>
using namespace std;

// 定义数组最大长度:题目中n和V的上限是1000,所以设1e3+10避免越界
const int N = 1e3 + 10;
int v[N]; // 存储每个物品的体积,v[i]表示第i个物品的体积
int w[N]; // 存储每个物品的价值,w[i]表示第i个物品的价值
// f[i][j] 核心定义:
// 在[1, i]个物品中挑选,所选物品总体积≤j时,能得到的最大价值
int f[N][N];  

int main(){
    // n是物品数量,m是背包最大容量(题目里的V)
    int n, m;
    // 输入物品数量和背包容量
    cin >> n >> m;
    
    // 循环输入每个物品的体积和价值(i从1开始,对应“前i个物品”的逻辑)
    for(int i = 1; i <= n; i++){
        cin >> v[i] >> w[i];
    }

    // ===================== 方案1:不要求装满背包,求最大价值 =====================
    // 遍历每个物品(i从1到n,处理第i个物品)
    for(int i = 1; i <= n; i++){
        // 遍历所有可能的背包容量(j从0到m,覆盖所有体积上限)
        for(int j = 0; j <= m; j++){
            // 第一步:不选第i个物品
            // 此时最大价值 = 前i-1个物品、体积≤j的最大价值(继承上一轮的结果)
            f[i][j] = f[i-1][j];
            
            // 如果当前背包容量j ≥ 第i个物品的体积v[i],说明能装下这个物品
            if(j >= v[i]){
                // 第二步:尝试选第i个物品,取“选”和“不选”中的最大值
                // 选第i个物品的价值 = 前i-1个物品、体积≤(j-v[i])的最大价值 + 第i个物品的价值
                // max(不选的价值, 选的价值) → 得到当前状态的最优解
                f[i][j] = max(f[i-1][j], f[i-1][j - v[i]] + w[i]);
            }
        }
    }
    // 输出方案1的结果:前n个物品、体积≤m(背包最大容量)的最大价值
    cout << f[n][m] << endl;

    // ===================== 方案2:要求恰好装满背包,求最大价值 =====================
    // 初始化数组f为-0x3f(十进制-63),表示“初始时所有状态都是非法的(无法恰好装满)”
    // memset是按字节赋值,-0x3f的绝对值小,不会溢出,适合表示“非法状态”
    memset(f, -0x3f, sizeof f);
    // 唯一的合法初始状态:0个物品、体积0,恰好装满,价值0
    f[0][0] = 0;

    // 重新遍历每个物品,计算“恰好装满”的状态(逻辑和方案1一致,只是初始化不同)
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++){
            // 第一步:不选第i个物品,继承前i-1个物品的状态
            f[i][j] = f[i-1][j];
            
            // 如果能装下第i个物品,尝试选它
            if(j >= v[i]){
                // 选第i个物品的价值 = 前i-1个物品、体积=(j-v[i])的最大价值 + 第i个物品的价值
                // 只有当f[i-1][j-v[i]]是合法的(≥-0x3f),选完后才可能合法
                f[i][j] = max(f[i-1][j], f[i-1][j - v[i]] + w[i]);
            }
        }
    }

    // 输出方案2的结果:
    // 如果f[n][m] < 0 → 说明无法恰好装满背包,按题目要求输出0
    // 否则 → 输出恰好装满时的最大价值
    if(f[n][m] < 0){
        cout << 0 << endl;
    }else{
        cout << f[n][m] << endl;
    }

    return 0;
}

//空间优化版

#include <iostream>
#include <cstring>
#include <algorithm> // max函数必须的头文件
using namespace std;

const int N = 1e3 + 10;
int v[N], w[N];
// 一维DP数组:f[j] 表示“总体积不超过j(方案1)/恰好为j(方案2)”时的最大价值
int f[N];  

int main(){
    int n, m;
    cin >> n >> m;
    // 输入每个物品的体积v[i]和价值w[i]
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];

    // ===================== 方案1:不要求装满背包 =====================
    // 一维DP默认初始化为0(全局数组,未赋值时默认0),符合“不装满”的初始化规则
    // 遍历每个物品
    for(int i = 1; i <= n; i++){
        // 逆序遍历体积:从m到v[i](核心!避免同一个物品被重复选)
        for(int j = m; j >= v[i]; j--){
            // 状态转移:
            // f[j] = max(不选第i个物品的价值, 选第i个物品的价值)
            // 不选:f[j](上一轮的结果,即前i-1个物品的最优解)
            // 选:f[j-v[i]] + w[i](留出v[i]体积装前i-1个物品,加当前物品价值)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    // 方案1结果:体积不超过m的最大价值
    cout << f[m] << endl;

    // ===================== 方案2:要求恰好装满背包 =====================
    // 初始化:所有状态设为-0x3f(-63),表示“非法(无法恰好装满)”
    memset(f, -0x3f, sizeof f);
    // 唯一合法初始状态:体积0,恰好装满,价值0
    f[0] = 0;

    // 重新遍历物品,计算“恰好装满”的最优解
    for(int i = 1; i <= n; i++){
        // 逆序遍历体积(和方案1一样,避免重复选物品)
        for(int j = m; j >= v[i]; j--){
            // 转移逻辑和方案1一致,只是初始状态不同
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

    // 输出方案2结果:
    // f[m] < 0 → 无法恰好装满,输出0;否则输出f[m]
    if(f[m] < 0){
        cout << 0 << endl;
    }else{
        cout << f[m] << endl;
    }

    return 0;
}
posted @ 2026-03-13 12:08  无酒无剑  阅读(22)  评论(0)    收藏  举报