自学内容网 自学内容网

01背包问题(不装满和装满)和完全背包问题

本文将详细讲解 01背包问题完全背包问题,包括问题定义、思路分析、状态转移方程以及代码实现。


01背包问题

问题定义

给定一个容量为 V 的背包,有 N 件物品,每件物品有一个体积 w[i] 和一个价值 v[i]。每种物品只能选择 0次或1次。求如何选择物品,使得装入背包的物品总价值最大。

解题思路

01背包问题是经典的动态规划问题,主要思路是 逐件考虑是否选择当前物品

状态定义
  • dp[j] 表示在背包容量为 j 时,能得到的最大价值。
状态转移方程
  • 如果不选择第 i 件物品:dp[j] = dp[j](容量和价值都不变)。
  • 如果选择第 i 件物品:dp[j] = dp[j - w[i]] + v[i](容量减少 w[i],价值增加 v[i])。
  • 综合考虑:dp[j] = max(dp[j], dp[j - w[i]] + v[i])
初始化
  • dp[0] = 0:容量为0时,最大价值为0。
  • 其他 dp[j] 初始化为0(或负无穷,视题目要求而定)。
优化
  • 由于每件物品只能用一次,物品需从大到小枚举容量 j(避免重复选择)。

代码实现

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

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

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

    vector<int> dp(V + 1, 0);

    // 使用动态规划求解
    for (int i = 1; i <= N; ++i) { // 枚举每件物品
        for (int j = V; j >= w[i]; --j) { // 从大到小枚举容量
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }

    cout << dp[V] << endl; // 输出容量为V时的最大价值
    return 0;
}

完全背包问题

问题定义

与01背包类似,给定一个容量为 V 的背包,有 N 件物品,每件物品有一个体积 w[i] 和一个价值 v[i]。但是每种物品可以选择 无限次。求如何选择物品,使得装入背包的物品总价值最大。

解题思路

完全背包问题的核心区别在于,每种物品可以选择 多次。状态转移和01背包类似,但在计算状态时,要允许重复选择同一件物品。

状态定义
  • dp[j] 表示在背包容量为 j 时,能得到的最大价值。
状态转移方程
  • 如果选择第 i 件物品:dp[j] = dp[j - w[i]] + v[i](容量减少 w[i],价值增加 v[i])。
  • 如果不选择第 i 件物品:dp[j] = dp[j]
  • 综合考虑:dp[j] = max(dp[j], dp[j - w[i]] + v[i])
优化
  • 由于每件物品可以重复选择,容量 j 应从小到大枚举(确保当前容量的状态可以多次使用同一件物品)。

代码实现

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

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

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

    vector<int> dp(V + 1, 0);

    // 使用动态规划求解
    for (int i = 1; i <= N; ++i) { // 枚举每件物品
        for (int j = w[i]; j <= V; ++j) { // 从小到大枚举容量
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }

    cout << dp[V] << endl; // 输出容量为V时的最大价值
    return 0;
}

两者对比

特性01背包问题完全背包问题
物品使用次数每件物品最多使用一次每件物品可以使用无限次
容量枚举顺序从大到小(避免重复选择同一件物品)从小到大(允许重复选择同一件物品)
状态转移公式dp[j] = max(dp[j], dp[j - w[i]] + v[i])dp[j] = max(dp[j], dp[j - w[i]] + v[i])

装满的01背包

下面将详细讲解 恰好装满背包问题物品有依赖关系的背包问题,包括问题定义、思路分析、状态转移方程、以及代码实现。


问题定义

给定一个容量为 V 的背包,有 N 件物品,每件物品有体积 w[i] 和价值 v[i]。每种物品只能使用一次。求如何选择物品,使得 恰好装满背包 的情况下,背包中的总价值最大。如果无法恰好装满背包,输出 0。


与普通 01 背包的区别

  • 普通 01 背包:只需要背包容量不超过 V,即可保证结果有效。
  • 恰好装满背包:必须严格要求背包容量 正好等于 V,否则结果无效。

解题思路

动态规划的核心思想还是类似于 01 背包,但需要特别处理 恰好装满 的约束。

状态定义
  • 定义 dp[j] 表示:容量为 j 的背包,所能装下的最大价值
    • 如果背包容量 恰好装满dp[V] 就是答案。
    • 如果无法恰好装满背包,则 dp[V] 的值为无效状态。
状态转移方程
  1. 如果不选第 i 件物品:
    dp[j] = dp[j](容量和价值都不变)。
  2. 如果选第 i 件物品:
    dp[j] = dp[j - w[i]] + v[i](减少容量,增加价值)。
  3. 综合考虑:
    dp[j] = max(dp[j], dp[j - w[i]] + v[i])
特殊处理:无效状态
  • 初始时,dp[j] = -∞(表示无效状态),只有 dp[0] = 0(容量为 0 时的初始状态有效)。
  • 最终结果如果 dp[V] == -∞,说明无法恰好装满背包。
初始化
  • dp[0] = 0:容量为 0 时,背包价值为 0。
  • 其他 dp[j] 初始化为负无穷(-∞),表示初始时无效状态。

代码实现

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

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

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

    vector<int> dp(V + 1, -1e9); // 初始化为无穷小
    dp[0] = 0; // 容量为 0 时,价值为 0

    // 动态规划过程
    for (int i = 1; i <= N; ++i) { // 枚举每件物品
        for (int j = V; j >= w[i]; --j) { // 从大到小枚举容量
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }

    // 输出结果
    if (dp[V] < 0) {
        cout << 0 << endl; // 无法恰好装满背包
    } else {
        cout << dp[V] << endl; // 恰好装满时的最大价值
    }

    return 0;
}

举例

输入:
N = 3, V = 5
物品 1: w = 1, v = 2
物品 2: w = 3, v = 4
物品 3: w = 4, v = 5
输出:
6
解释:
  • 恰好装满容量为 5 的背包,选择物品 1 和物品 2,总价值为 6。

物品有依赖关系的背包问题

问题定义

给定一个容量为 V 的背包,有 N 件物品,每件物品有体积 w[i] 和价值 v[i]。此外,每件物品还有一个附加属性 parent[i]

  • 如果 parent[i] = 0,表示该物品是一个 主物品
  • 如果 parent[i] > 0,表示该物品是 从属于第 parent[i] 件物品的附件
要求
  • 如果选择了某个附件物品,必须先选择它的主物品。
  • 一个主物品最多可以选择 2 个附件。

解题思路

这是一个 树形依赖的多重背包问题,可以通过 分组背包 的思想解决:

  1. 将每个主物品及其附件看作一个组。
  2. 每个组内的状态转移,枚举主物品和附件的组合情况。
状态定义
  • 定义 dp[j] 表示:容量为 j 的背包,所能装下的最大价值
状态转移方程
  • 对于每个主物品及其附件,枚举所有可能的组合:
    • 只选主物品
    • 选主物品 + 一个附件
    • 选主物品 + 两个附件
  • 更新状态 dp[j]

分组处理
  1. 遍历所有物品,将主物品及其附件分组。
  2. 对每一组物品,枚举容量 j 和组合情况,更新 dp[j]

代码实现

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

struct Item {
    int w, v; // 物品的体积和价值
    int parent; // 主从关系
};

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

    vector<Item> items(N + 1);
    for (int i = 1; i <= N; ++i) {
        cin >> items[i].w >> items[i].v >> items[i].parent;
    }

    vector<int> dp(V + 1, 0);

    // 遍历所有主物品
    for (int i = 1; i <= N; ++i) {
        if (items[i].parent == 0) { // 主物品
            vector<pair<int, int>> group; // 当前组
            group.push_back({items[i].w, items[i].v}); // 主物品加入组

            // 找到所有附件
            for (int j = 1; j <= N; ++j) {
                if (items[j].parent == i) {
                    group.push_back({items[j].w, items[j].v});
                }
            }

            // 处理当前组
            for (int j = V; j >= 0; --j) { // 从大到小枚举容量
                for (auto& item : group) { // 枚举组内所有物品
                    if (j >= item.first) {
                        dp[j] = max(dp[j], dp[j - item.first] + item.second);
                    }
                }
            }
        }
    }

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

举例

输入:
N = 5, V = 10
物品 1: w = 5, v = 10, parent = 0 (主物品)
物品 2: w = 4, v = 7, parent = 1 (附件)
物品 3: w = 3, v = 6, parent = 1 (附件)
物品 4: w = 6, v = 12, parent = 0 (主物品)
物品 5: w = 2, v = 4, parent = 4 (附件)
输出:
23
解释:
  • 选择主物品 1 和它的附件(物品 3),主物品 4 和它的附件(物品 5),总价值为 23。

总结

  1. 恰好装满背包问题:用 -∞ 初始化,动态规划时确保状态只更新到恰好装满的容量。
  2. 物品有依赖关系的背包问题:将主物品和附件分组,枚举组内的组合情况,动态更新状态。

扩展知识

  1. 多重背包问题:每种物品有一个限制使用次数的上限,可以将每种物品拆分成若干个01背包问题来解决,或者用二进制优化。
  2. 混合背包问题:有的物品是01背包,有的是完全背包,甚至有的是多重背包。
  3. 背包问题变种:比如要求恰好装满背包、物品有依赖关系等。

如果你有其他问题,欢迎随时提问!


原文地址:https://blog.csdn.net/2303_80261633/article/details/144300351

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!