今天我们来解决一个有趣的动态规划问题——书架摆放优化。这个问题来自USACO竞赛,要求我们在给定书籍尺寸和书架宽度限制的情况下,找到最优的书籍摆放方式,使得整个书架的总高度最小。
我们有N本书,每本书有两个属性:
摆放规则:
目标是找到使总高度最小的摆放方案。
这个问题具有典型的动态规划特征:
动态规划特别适合这种需要做出系列决策(如何分层)且决策会影响后续状态的问题。我们可以定义状态f[i]表示前i本书的最小总高度,然后通过状态转移方程逐步构建解。
我们定义两个关键数组:
初始化:
提示:前缀和数组可以快速计算任意区间[i,j]的宽度总和,这是优化时间复杂度的关键。
对于每本书i(从1到n):
核心转移方程:
f[i] = min(f[i], f[j-1] + max{h[j..i]}) 对于所有j ≤ i且sum{w[j..i]} ≤ L
对于n=2000,n^2=4,000,000,在现代计算机上完全可以接受。
cpp复制#include <bits/stdc++.h>
using namespace std;
const int N = 2002;
int n, l, h[N], w[N], c[N], f[N];
int main() {
cin >> n >> l;
for(int i = 1; i <= n; i++) {
cin >> h[i] >> w[i];
c[i] = c[i - 1] + w[i]; // 前缀和计算
f[i] = INT_MAX; // 初始化为极大值
}
for(int i = 1; i <= n; i++) {
f[i] = f[i - 1] + h[i]; // 初始情况:单独一层
int max_h = h[i]; // 当前层的最大高度
for(int j = i - 1; j >= 1; j--) {
if(c[i] - c[j - 1] > l) break; // 宽度超过限制
max_h = max(max_h, h[j]); // 更新最大高度
f[i] = min(f[i], f[j - 1] + max_h); // 状态转移
}
}
cout << f[n];
return 0;
}
输入处理:
动态规划主循环:
状态转移:
输出结果:
输入:
code复制5 10
5 7
9 2
8 5
13 2
3 8
计算过程:
逐步计算f[i]:
但实际最优解是:
看起来我们的计算与示例不符,这说明需要重新审视状态转移过程。
问题出在我们的状态转移没有正确考虑所有可能的分段方式。让我们重新定义:
f[i] = min(f[j] + max{h[j+1..i]}) 对于所有j < i且sum{w[j+1..i]} ≤ L
修正后的计算:
这样得到了正确结果21。
cpp复制#include <bits/stdc++.h>
using namespace std;
const int N = 2002;
int n, l, h[N], w[N], c[N], f[N];
int main() {
cin >> n >> l;
for(int i = 1; i <= n; i++) {
cin >> h[i] >> w[i];
c[i] = c[i - 1] + w[i];
f[i] = INT_MAX;
}
for(int i = 1; i <= n; i++) {
int max_h = 0;
for(int j = i; j >= 1; j--) {
if(c[i] - c[j - 1] > l) break;
max_h = max(max_h, h[j]);
f[i] = min(f[i], f[j - 1] + max_h);
}
}
cout << f[n];
return 0;
}
错误的分段方式:
初始化问题:
边界条件:
整数溢出:
打印中间结果:
小规模测试:
对比暴力解:
可视化分段:
单调队列优化:
空间优化:
输入优化:
书籍可以重新排序:
多层高度限制:
书架有固定层数:
资源分配:
文本排版:
内存管理:
推荐题目:
进阶算法:
参考书籍: