跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

动态规划:01 背包详解与空间优化

01 背包问题是动态规划的经典模型,核心在于状态定义与转移。讲解如何定义二维状态 dp[i][j] 表示前 i 个物品装入容量 j 的最大价值,推导选或不选的状态转移方程。针对恰好装满的情况,通过初始化负无穷或特定标记值区分可行解。重点介绍滚动数组优化,将空间复杂度从 O(nV) 降至 O(V),并通过逆序遍历一维数组避免状态覆盖。提供完整的 C++ 代码实现及模板。

古灵精怪发布于 2026/3/26更新于 2026/9/166 浏览
动态规划:01 背包详解与空间优化

背包问题概述

背包问题是动态规划的一类经典问题。其本质是一种组合优化的 NP 完全问题。问题描述为:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,如何选择才能使物品的总价值最高。

根据物品个数限制,背包问题分为以下几类:

  • 01 背包问题:每个物品只有一个
  • 完全背包问题:每个物品有无限多个
  • 多重背包问题:每件物品有有限个
  • 混合背包问题:包含上述三种情况
  • 分组背包问题:物品分 n 组,每组最多选一个

此外,根据背包是否装满可分为'不一定装满'和'一定装满';根据限定条件个数可分为'普通背包'和'二维费用背包'。

尽管分类繁多,大多是从 01 背包演化而来,因此掌握 01 背包至关重要。背包问题的优化通常涉及空间复杂度的降低,常见方案包括滚动数组优化。

01 背包(medium)

题目描述: 你有一个背包,最多能容纳的体积是 V。现在有 n 个物品,第 i 个物品的体积为 vi,价值为 wi。

  1. 求这个背包至多能装多大价值的物品?
  2. 若背包恰好装满,求至多能装多大价值的物品?

输入描述: 第一行两个整数 n 和 V。接下来 n 行,每行两个数 vi 和 wi。 其中 1 ≤ n, V, vi, wi ≤ 1000。

输出描述: 输出两行,第一行为第一问答案,第二行为第二问答案(无解输出 0)。

1、第一问解题思路

背包问题的本质是动态规划。按照五部曲解析:

状态表示

定义 dp[i][j] 表示从前 i 个物品中选择,且物品总体积不超过 j 时,能装入背包的最大物品价值。

状态转移方程

对于第 i 个物品,有两种选择:

  1. 不选:dp[i][j] = dp[i - 1][j]
  2. 选(需满足 j >= v[i]):dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i])
初始化

虚拟行列初始化为 0。dp[0][j] 表示无物品,价值为 0;dp[i][0] 表示背包容量为 0,价值为 0。

遍历顺序

从上往下,从左往右遍历。

返回值

返回 dp[n][V]。

2、第二问解题思路

第二问要求背包恰好装满,需在第一问基础上修改细节。

状态表示修改

dp[i][j] 表示从前 i 个物品中选择,且物品总体积正好等于背包空间 j 时的最大价值。

状态转移方程细节修改

判断 dp[i - 1][j - v[i]] 是否能构成 j - v[i] 的空间。设定 表示无法凑成对应体积,非 -1 则为最大价值。

dp[i][j] == -1
初始化修改

dp[0][0] 初始化为 0,其余 dp[0][j] 初始化为 -1,表示初始状态下只有容量 0 是可行的。

代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1010;
int n, V, v[N], w[N];
int dp[N][N];

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

    // 填入题目给出的物品体积和价值的数组
    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i];
    }

    // 解决第一问:求这个背包至多能装多大价值的物品
    // 不需要特殊初始化,全局变量默认为 0
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= V; ++j) {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i]) {
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
            }
        }
    }
    cout << dp[n][V] << endl;

    // 解决第二问:若背包恰好装满,求至多能装多大价值的物品
    memset(dp, 0, sizeof(dp));
    // 第一行除了第一个元素之外其它元素初始化为 -1
    for (int i = 1; i <= V; ++i) {
        dp[0][i] = -1;
    }

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= V; ++j) {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i] && dp[i - 1][j - v[i]] != -1) {
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
            }
        }
    }
    cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;

    return 0;
}

优化

背包问题通常利用「滚动数组」进行空间优化。原二维数组 dp[i][j] 依赖 dp[i - 1][j] 和 dp[i - 1][j - v[i]],即上一行的数据。因此可以压缩为一维数组 dp[j]。

滚动数组原理

使用一维数组时,若从左往右遍历,更新 dp[j] 时会用到已更新的 dp[j - v[i]],这会导致当前物品被重复选取(类似完全背包)。因此,必须从右往左遍历,确保使用的是上一轮的数据。

优化操作总结

  1. 删除横坐标 i,只保留一维数组 dp[j]。
  2. 修改 j 的遍历顺序为从大到小(V 到 v[i])。
  3. 时间上,只需遍历到 v[i] 即可停止。

优化后的代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1010;
int n, V, v[N], w[N];
int dp[N];

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

    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i];
    }

    // 解决第一问:求这个背包至多能装多大价值的物品
    // 不需要初始化,全局变量默认为 0
    for (int i = 1; i <= n; ++i) {
        for (int j = V; j >= v[i]; --j) {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[V] << endl;

    // 解决第二问:若背包恰好装满,求至多能装多大价值的物品
    memset(dp, 0, sizeof(dp));
    // 初始化除 dp[0] 外为 -1
    for (int j = 1; j <= V; ++j) {
        dp[j] = -1;
    }

    for (int i = 1; i <= n; ++i) {
        for (int j = V; j >= v[i]; --j) {
            if (dp[j - v[i]] != -1) {
                dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
            }
        }
    }
    cout << (dp[V] == -1 ? 0 : dp[V]) << endl;

    return 0;
}

目录

  1. 背包问题概述
  2. 01 背包(medium)
  3. 1、第一问解题思路
  4. 状态表示
  5. 状态转移方程
  6. 初始化
  7. 遍历顺序
  8. 返回值
  9. 2、第二问解题思路
  10. 状态表示修改
  11. 状态转移方程细节修改
  12. 初始化修改
  13. 代码
  14. 优化
  15. 滚动数组原理
  16. 优化操作总结
  17. 优化后的代码

更多推荐文章

查看全部
  • GitHub 双重身份验证(2FA)配置指南
  • AI 深度观察:GTC 开幕、Agent 工程化与具身智能新进展
  • 双足机器人 2-RSS-1U 并联踝关节设计与运动学分析
  • C++ 新手学习指南:从环境搭建到核心概念
  • AI 大模型提示工程(Prompt)核心技巧与工具详解
  • 大模型提示工程:掌握提问驱动 AI
  • 线性 DP 五大经典模型:LIS、LCS、合唱队形、编辑距离详解与模板
  • 零基础转行网络安全:Web 安全入门与学习路线指南
  • 大模型与 AIGC 概述及基础知识
  • Spring Security 6.3.x 使用指南
  • GitHub Desktop 界面中文汉化指南
  • C++ 哈希表详解:开散列与闭散列
  • 人工智能入门:常见术语解释与误区澄清
  • Python AI 大模型部署指南:本地运行、API 服务与 Docker 封装
  • 基于 AR 眼镜的春节亲戚称呼助手实现
  • SQL 自动生成 ER 图与数据库设计基础
  • JDK 21 安装与环境变量配置指南(Windows)
  • 大模型测评:七款工具降英文 AI 率横向对比
  • C++ 类与对象全面剖析:构造函数深化与静态成员特性
  • C/C++ 运行时库概念详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online