当前位置:首页 > C++目录 > 正文内容

01背包问题

亿万年的星光3个月前 (10-03)C++目录37826
  1. 问题定义

01背包问题是一个经典的组合优化问题,通常描述如下:

  • 有个容量为C的背包

  • 有n件物品,第i件物品的重量为Wi,价值为Vi

  • 每种物品只有一件,可以选择放入背包(1)不放入背包(0),因此称为“01”背包。

  • 目标:在不超过背包容量的前提下,使装入背包的物品总价值最大。


2.动态规划解法


这是最常用的解法,核心是状态定义状态转移方程

(1)状态定义

dp[i][j]dp[i][j]表示:考虑前 i 件物品,在背包容量为 j 时能获得的最大价值。

  • i 的范围:0in

  • j 的范围:0jC

(2)状态转移

对于第 i 件物品(体积 v[i],价值 w[i]):

  1. 不选dp[i][j] = dp[i-1][j]

  2. (前提是 j >= v[i]):dp[i][j] = dp[i-1][j - v[i]] + w[i]

取两者最大值:

                dp[i][j]=max(dp[i−1][j], dp[i−1][j−v[i]]+w[i])

dp[i][j]=max(dp[i1][j], dp[i1][jv[i]]+w[i])

(3)初始化

  • dp[0][j] = 0(没有物品可选时,价值为 0)

  • dp[i][0] = 0(背包容量为 0 时,无法装任何物品)

(4)参考代码


#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int N, V;
    cin >> N >> V;
    
    int v[1005], w[1005];
    for (int i = 1; i <= N; i++) {
        cin >> v[i] >> w[i];
    }

    // dp[i][j] 表示前 i 件物品,容量 j 的最大价值
    int dp[1005][1005] = {0};

    for (int i = 1; i <= N; i++) {
        for (int j = 0; j <= V; j++) { // 从 0 开始更规范
            if (j < v[i]) {
                // 容量不够,只能不选
                dp[i][j] = dp[i - 1][j];
            } else {
                // 容量足够,取“不选”和“选”的最大值
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
            }
        }
    }

    cout << dp[N][V] << endl;
    return 0;
}


  • 当 j < v[i]:背包容量不够,不能选第 i 件物品,所以 dp[i][j] 直接等于 dp[i-1][j]

  • 当 j >= v[i]:可以选第 i 件物品,所以要取 “选” 和 “不选” 的最大值。




(5)例子详解

如果输入

4 5
1 2
2 4
3 4
4 5



物品编号体积 v[i]价值 w[i]
112
224
334
445

背包容量: V = 5

开始逐步计算dp表

初始状态(i=0):

容量 j: 0 1 2 3 4 5
dp[0]:  0 0 0 0 0 0

i=1(考虑第1件物品:体积1,价值2)

j=1: max(dp[0][1]=0, dp[0][0]+2=2) = 2
j=2: max(dp[0][2]=0, dp[0][1]+2=2) = 2
j=3: max(dp[0][3]=0, dp[0][2]+2=2) = 2
j=4: max(dp[0][4]=0, dp[0][3]+2=2) = 2
j=5: max(dp[0][5]=0, dp[0][4]+2=2) = 2

dp[1]: 0 2 2 2 2 2

i=2(考虑第2件物品:体积2,价值4)

j=1: dp[1][1]=2 (容量不够选物品2)
j=2: max(dp[1][2]=2, dp[1][0]+4=4) = 4
j=3: max(dp[1][3]=2, dp[1][1]+4=6) = 6
j=4: max(dp[1][4]=2, dp[1][2]+4=6) = 6
j=5: max(dp[1][5]=2, dp[1][3]+4=6) = 6

dp[2]: 0 2 4 6 6 6

i=3(考虑第3件物品:体积3,价值4)

j=1: dp[2][1]=2 (容量不够)
j=2: dp[2][2]=4 (容量不够)
j=3: max(dp[2][3]=6, dp[2][0]+4=4) = 6
j=4: max(dp[2][4]=6, dp[2][1]+4=6) = 6
j=5: max(dp[2][5]=6, dp[2][2]+4=8) = 8

dp[3]: 0 2 4 6 6 8


i=4(考虑第4件物品:体积4,价值5)

j=1: dp[3][1]=2 (容量不够)
j=2: dp[3][2]=4 (容量不够)
j=3: dp[3][3]=6 (容量不够)
j=4: max(dp[3][4]=6, dp[3][0]+5=5) = 6
j=5: max(dp[3][5]=8, dp[3][1]+5=7) = 8

dp[4]: 0 2 4 6 6 8

最终结果:

最大价值: dp[4][5] = 8

最优方案: 选择物品2(体积2,价值4)和物品3(体积3,价值4),总价值8,刚好用完容量5。


完整的dp表

i\j012345
0000000
1022222
2024666
3024668
4024668


关键点:

  1. dp[i][j] = dp[i-1][j]:不选当前物品,继承前i-1件物品的最优解

  2. dp[i-1][j-v[i]] + w[i]:选当前物品,用剩余容量 j-v[i] 在前i-1件物品中找最优解,加上当前物品价值

  3. max(...):在"选"和"不选"中选择价值更大的方案

   


3.一维数组优化

观察状态转移方程

                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);

发现 dp[i] 只依赖于 dp[i-1],所以可以只用一维数组,倒序遍历容量,避免覆盖上一层的状态


一维 dp 定义:

dp[j] 表示容量为 j 的背包能装的最大价值。

状态转移

dp[j]=max(dp[j], dp[j−v[i]]+w[i])

注意:jVv[i] 倒序遍历。


参考代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int N, V;
    cin >> N >> V;
    vector<int> v(N + 1), w(N + 1);
    for (int i = 1; i <= N; i++) {
        cin >> v[i] >> w[i];
    }

    vector<int> dp(V + 1, 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;
    return 0;
}


扫描二维码推送至手机访问。

版权声明:本文由青少年编程知识记录发布,如需转载请注明出处。

分享给朋友:

相关文章

【题解】组合数学

【题解】组合数学

一、排列与组合口诀:有序排列,无序组合,分类相加,分步相乘。1.排列数公式:表示的含义是从n个数中选出m个进行排队,有多少种不同的排法。从n个不同的元素中任取m(m≤n)个元素的所有排列的个数,叫做从...

C++中的max和min函数(最大值,最小值)

1.头文件      最大值最小值函数所在头文件是#include<algorithm>2.用法#include<iostream> #incl...

树的存储与遍历—顺序存储

顺序存储使用数组来存储二叉树节点,通过数组下标表示节点间的父子关系,一般适用于完全二叉树。1.存储规则根节点存储在索引 0 位置对于索引为 i 的节点:左子节点索引:2*i + 1右子节点索引:2*i...

组合数的写法

前面我们写过 全排列和排列数 等。这篇文章。我们写一下组合数。例题:从n个数中,选出m个,一共有多少种不同的选法?这是一道典型的组合数公式。我们直接用dfs公式肯定会出现重复的。#include<...

【算法】扩展欧几里得算法

一、欧几里得算法我们前面学过求最大公约数的算法:欧几里得算法(又叫辗转相除法) ,一般缩写是gcd,在C++中经常写成如下形式:int gcd(int a,int b)...

【高级篇】C++ 中string的用法

【高级篇】C++ 中string的用法

0.概述string是C++标准库的一个重要部分,本意是字符串,和字符数组不同的是,字符数组是通过一个一个字符模拟的字符串,而string本身就是字符串,string在处理字符串问题时,十分强大。1....