1. 问题分析与建模
这道题目描述了一个有趣的奶酪塔构建问题,我们需要在高度限制下最大化奶酪塔的总价值。关键在于理解题目中"大奶酪"对下方奶酪高度的影响机制。
1.1 问题核心要素解析
首先明确几个关键概念:
- 奶酪类型:共有N种奶酪,每种有高度Hi和价值Vi
- 大奶酪判定:高度≥K的奶酪被定义为大奶酪
- 高度压缩规则:如果一个奶酪上方存在大奶酪(无论多少块),这个奶酪的实际高度会变为原来的4/5
1.2 解题思路分解
这个问题可以分解为两种基本情况:
- 塔顶没有大奶酪:此时所有奶酪保持原始高度,直接使用完全背包算法求解
- 塔顶有大奶酪:此时需要考虑高度压缩的影响,需要特殊处理
2. 动态规划解法详解
2.1 完全背包基础解法
对于第一种情况(塔顶无大奶酪),这是一个标准的完全背包问题:
- 背包容量:塔的最大高度T
- 物品:各种奶酪
- 目标:在不超过高度限制的情况下最大化价值
cpp复制for(int i=1;i<=n;i++) {
for(int j=h[i];j<=t;j++)
dp[j] = max(dp[j], dp[j-h[i]]+v[i]);
}
ans = dp[t];
2.2 考虑大奶酪的特殊处理
当塔顶有大奶酪时,需要特殊处理:
- 枚举每一种可能的大奶酪作为塔顶
- 计算剩余可用高度:(T - 大奶酪高度) × 5/4
- 在这个压缩后的高度限制下求最大价值
cpp复制for(int i=1;i<=n;i++) {
if(h[i] >= k) {
int remaining = (t - h[i]) * 5 / 4;
ans = max(ans, dp[remaining] + v[i]);
}
}
2.3 为什么需要5/4的转换
这里的关键在于理解高度压缩的影响:
- 当有大奶酪在顶部时,下方所有奶酪的高度都被压缩为原来的4/5
- 因此,实际占用的高度 = 原始高度 × 4/5
- 反过来,在规划时,我们需要预留的空间 = 需要的高度 × 5/4
3. 算法实现细节
3.1 预处理与初始化
cpp复制int n, t, k, v[1001], h[1001], ans, dp[10001];
memset(dp, 0, sizeof(dp));
3.2 完全背包部分
注意背包容量需要扩大到t×5/4,因为要考虑最坏情况下所有奶酪都被压缩:
cpp复制for(int i=1;i<=n;i++) {
for(int j=h[i];j<=t*5/4;j++)
dp[j] = max(dp[j], dp[j-h[i]]+v[i]);
}
3.3 边界条件处理
需要考虑几种特殊情况:
- 当T < 所有奶酪高度时,无法构建任何塔
- 当没有大奶酪时,直接返回完全背包结果
- 当所有奶酪都是大奶酪时,只能选择一块
4. 复杂度分析与优化
4.1 时间复杂度
- 完全背包部分:O(N × T × 5/4)
- 枚举大奶酪部分:O(N)
- 总体复杂度:O(NT),在题目给定的约束下完全可行
4.2 空间优化
可以使用一维滚动数组优化空间:
cpp复制int dp[10001]; // 只需一维数组
5. 常见错误与调试技巧
5.1 典型错误案例
-
高度计算错误:
- 忘记处理5/4的转换
- 错误地将所有奶酪都压缩,而不仅仅是塔顶大奶酪下方的
-
边界条件遗漏:
- 没有考虑T=0的情况
- 没有处理所有奶酪高度都大于T的情况
5.2 调试建议
-
打印中间结果:
cpp复制cout << "DP array: "; for(int i=0;i<=t;i++) cout << dp[i] << " "; cout << endl; -
构造测试用例:
- 小规模测试用例(如样例)
- 极端情况(如N=1,T=5等)
6. 算法扩展思考
这个问题可以延伸出几个有趣的变种:
- 如果高度压缩不是固定的4/5,而是根据大奶酪的数量变化?
- 如果允许奶酪旋转(改变高度)?
- 如果有多层大奶酪,压缩效果叠加?
这些变种会增加问题的复杂度,可能需要更复杂的动态规划状态设计。
7. 完整代码实现
cpp复制#include<bits/stdc++.h>
using namespace std;
int main() {
int n, t, k, v[101], h[101], ans = 0, dp[5005] = {0};
cin >> n >> t >> k;
for(int i = 1; i <= n; i++) {
cin >> v[i] >> h[i];
// 完全背包预处理
for(int j = h[i]; j <= t * 5 / 4; j++) {
dp[j] = max(dp[j], dp[j - h[i]] + v[i]);
}
}
ans = dp[t]; // 情况1:没有大奶酪在顶部
// 情况2:枚举顶部的大奶酪
for(int i = 1; i <= n; i++) {
if(h[i] >= k) { // 是大奶酪
int remaining = (t - h[i]) * 5 / 4;
if(remaining >= 0) {
ans = max(ans, dp[remaining] + v[i]);
}
}
}
cout << ans << endl;
return 0;
}
在实际编程竞赛中,这种将问题分解为多个子问题并组合解决的思想非常常见。理解每个子问题的本质和它们之间的关系,是解决复杂动态规划问题的关键。