资源分配模型 (背包 DP)
算法简介
资源分配模型(即背包 DP)主要解决 在有限资源限制下,如何选择物品以达到目标最优 的一类问题。核心思想是将“资源”作为 DP 的状态(容量),枚举“物品”的选择策略进行转移。
- 时间复杂度:通常为 $O(N \cdot V)$,其中 $N$ 为物品数量,$V$ 为资源总量。
- 基本枚举顺序:通常先枚举物品 $i$,再枚举资源容量 $j$,最后枚举决策。
- 0/1 背包:容量 $j$ 必须 从大到小 (倒序) 枚举,防止同物品被重复累加。
- 完全背包:容量 $j$ 必须 从小到大 (正序) 枚举,利用已更新的状态实现叠加。
---
一、基础背包模型
1.1 0/1 背包 & 1.2 完全背包
这是所有背包问题的基石。区别在于每种物品是 仅有一件 还是 有无限件。
// 通用模板:根据题意选择内层循环方向
for (int i = 1; i <= n; i++) {
// 【0/1 背包】:倒序
for (int j = V; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
/* // 【完全背包】:正序
for (int j = w[i]; j <= V; j++) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
*/
}1.3 多重背包 (基础)
每种物品有数量限制 $c_i$。最基础的做法是将其展开为 $c_i$ 个 0/1 背包物品直接求解(若 $c_i$ 很大请参考第三部分的二进制拆分优化)。
for (int i = 1; i <= n; i++) {
// 基础展开:死算,复杂度 O(V * Σc_i)
for (int k = 1; k <= c[i]; k++) {
for (int j = V; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
}1.4 分组背包
物品被分为 $K$ 组,每组 最多选 1 件。核心在于 组内决策互斥,组间独立。
for (int k = 1; k <= groups; k++) { // 1. 枚举组
for (int j = V; j >= 0; j--) { // 2. 枚举容量 (倒序)
for (auto item : group[k]) { // 3. 枚举组内物品
if (j >= item.w)
dp[j] = max(dp[j], dp[j - item.w] + item.v);
}
}
}---
二、资源分配模型 (背包模型进阶)
【核心辨析】 资源分配 vs 传统背包
在进阶题目中,“背包”往往非常抽象,不再是“往包里塞东西”。我们需要建立如下映射:
* 问题:给定 $n$ 个点,可以自由添加 $k$ 个点,求最长上升点列。
* 转化:
* 资源 (背包容量):我们可以自由添加点的次数 $k$。
* 物品 (消耗):连接两个原有点 $P_i$ 和 $P_j$ 所需要的中间空缺距离 $d$(即曼哈顿距离 - 1)。
* 价值:点列长度增加了 $d+1$。
* 区别:这里没有“装进包里的物品”,只有“消耗 $k$ 值来换取连接”的决策。
---
三、背包常见优化
3.1 二进制拆分 (多重背包优化)
当物品数量 $c_i$ 很大时,将 $c_i$ 拆解为 $1, 2, 4, \dots, 2^k, \text{余数}$。任意 $\le c_i$ 的数量都可由这些组合而成,从而转化为 $O(N \log C \cdot V)$ 的 0/1 背包。
// 二进制拆分核心逻辑
vector<int> weights, values;
for (int i = 1; i <= n; i++) {
int count = c[i], k = 1;
while (k <= count) {
weights.push_back(k * w[i]);
values.push_back(k * v[i]);
count -= k;
k *= 2;
}
if (count > 0) { // 处理余数
weights.push_back(count * w[i]);
values.push_back(count * v[i]);
}
}
// 之后对 weights/values 做一次标准 0/1 背包即可3.2 维度交换 (值域优化)
针对 求最值 问题,当容量 $V$ 极大 ($10^9$) 但价值较小 ($10^5$) 时,交换 DP 的下标与值。定义 $dp[v]$ 为获得价值 $v$ 的 最小消耗。
// 初始化:dp[0] = 0, 其他为 INF
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = max_val_sum; j >= v[i]; j--) {
dp[j] = min(dp[j], dp[j - v[i]] + w[i]);
}
}
// 统计:从大到小找满足 dp[i] <= 背包容量的最大 i3.3 Bitset 优化 (可行性)
针对 可行性 问题,当数据范围极大 ($N \cdot V \approx 10^8$) 时,利用位运算并行处理状态转移。
#include <bitset>
bitset<100005> dp;
dp[0] = 1;
for (int i = 1; i <= n; i++) {
dp |= (dp << w[i]); // 一行代码完成所有转移
}3.4 贪心排序优化
针对 物品选择顺序影响结果 的问题(如含截止时间、高度限制、函数复合),先推导不等式排序,消除动态变量。
struct Item { int a, b; };
// 示例:ABC366 F (函数复合最大值)
bool cmp(Item x, Item y) {
return (x.a - 1) * y.b > (y.a - 1) * x.b; // 推导出的不等式
}
sort(items.begin(), items.end(), cmp);
// 排序后进行 DP【前置衔接知识点】
1、动态规划基础
【思维导图】

【题目知识点分类】
01
背包选礼物
普及-
--
练习
02
开心的金明
普及-
--
练习
03
疯狂的采药
普及-
--
练习
04
[NOIP 2012 普及组] 摆花
普及-
--
练习
05
越越的组队
普及-
--
练习
06
L 国的战斗之间谍
普及-
--
练习
07
砝码称重
普及/提高-
--
练习
08
音量调节
普及-
--
练习
09
[USACO09OCT] Bessie's Weight Problem G
普及-
--
练习
10
A+B Problem(再升级)
普及-
--
练习
11
[USACO09MAR] Cow Frisbee Team S
普及/提高-
--
练习
12
Knapsack 2
普及/提高-
--
练习
13
塔
普及+/提高
--
练习
14
[USACO08NOV] Buying Hay S
普及/提高-
--
练习
15
[USACO08MAR] River Crossing S
普及/提高-
--
练习
16
找啊找啊找GF
普及/提高-
--
练习
17
积木城堡
普及/提高-
--
练习
18
[USACO03FALL] Cow Exhibition G
普及/提高-
--
练习
19
[USACO05MAR] Space Elevator 太空电梯
普及/提高-
--
练习
20
[USACO3.1] 邮票 Stamps
普及/提高-
--
练习
21
赛斯石
普及+/提高
--
练习
22
「BJOI2019」排兵布阵
普及+/提高
--
练习
23
「CSP-S 2019」Emiya 家今天的饭
提高+/省选-
--
练习
24
彩色球
普及+/提高
--
练习
25
[CSP-J 2025] 多边形 / polygon
普及/提高-
--
练习
26
Maple 与树之美 (困难版)
普及+/提高
--
练习
27
Maple 与树之美 (简单版)
普及+/提高
--
练习
28
复合函数最大值
普及+/提高
--
练习
29
矩形涂色
普及+/提高
--
练习
30
兄妹
普及+/提高
--
练习