题目描述
查理参加了一个糖果采摘比赛,场地由 MMM 行 NNN 列的盒子组成,每个盒子包含一定数量的糖果。查理可以选择任意一个盒子并获得其中的糖果,但选择后:
- 同一行中相邻的左右盒子会被清空
- 相邻的上下两行中的所有盒子会被清空
比赛持续进行,直到没有糖果剩下。目标是找出查理能获得的最大糖果数量。
输入约束:
- 1≤M×N≤1051 \leq M \times N \leq 10^51≤M×N≤105
- 每个盒子初始糖果数在 111 到 10310^3103 之间
题目分析
这个问题可以分解为两个相互关联的约束条件:
- 行内约束:在同一行中选择盒子时,不能选择相邻的盒子
- 行间约束:选择某一行的盒子后,相邻的上下两行将不可用
这实际上形成了一个双层动态规划问题:
- 第一层:解决每行内部的最大值问题(类似"打家劫舍"问题)
- 第二层:解决行间的最大值问题(同样是"打家劫舍"问题)
解题思路
第一步:行内最大值计算
对于每一行,我们需要找到选择不相邻盒子所能获得的最大糖果数。这可以通过动态规划解决:
设对于当前行的某个位置:
- dp0dp_0dp0:不选择当前盒子时的最大值
- dp1dp_1dp1:选择当前盒子时的最大值
状态转移方程为:
- 不选当前盒子:newDp0=max(dp0,dp1)newDp_0 = \max(dp_0, dp_1)newDp0=max(dp0,dp1)
- 选择当前盒子:newDp1=dp0+candiesnewDp_1 = dp_0 + candiesnewDp1=dp0+candies
遍历完该行后,该行的最大值就是 max(dp0,dp1)\max(dp_0, dp_1)max(dp0,dp1)
第二步:行间最大值计算
得到每行的最大值后,我们得到一个大小为 MMM 的数组 rowValuesrowValuesrowValues,其中 rowValues[i]rowValues[i]rowValues[i] 表示第 iii 行能获得的最大糖果数。
现在问题转化为:从这个数组中选择不相邻的元素,使得总和最大。这同样使用"打家劫舍"问题的解法:
设:
- finalDp0finalDp_0finalDp0:不选择当前行时的最大值
- finalDp1finalDp_1finalDp1:选择当前行时的最大值
状态转移:
- 不选当前行:newFinalDp0=max(finalDp0,finalDp1)newFinalDp_0 = \max(finalDp_0, finalDp_1)newFinalDp0=max(finalDp0,finalDp1)
- 选择当前行:newFinalDp1=finalDp0+rowValues[i]newFinalDp_1 = finalDp_0 + rowValues[i]newFinalDp1=finalDp0+rowValues[i]
最终答案就是 max(finalDp0,finalDp1)\max(finalDp_0, finalDp_1)max(finalDp0,finalDp1)
算法复杂度
- 时间复杂度:O(M×N)O(M \times N)O(M×N),需要遍历每个盒子一次
- 空间复杂度:O(N)O(N)O(N),只需要存储当前行的数据
这种方法高效地利用了动态规划,将复杂的三维约束问题分解为两个一维的动态规划问题。
代码实现
// Candy
// UVa ID: 12146
// Verdict: Accepted
// Submission Date: 2025-11-08
// UVa Run Time: 0.020s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int numRows, numCols;
while (cin >> numRows >> numCols && (numRows != 0 || numCols != 0)) {
vector<int> rowValues(numRows);
for (int i = 0; i < numRows; ++i) {
vector<int> currentRow(numCols);
for (int j = 0; j < numCols; ++j) {
cin >> currentRow[j];
}
// 计算这一行的最大值 (House Robber 问题)
int dp0 = 0; // 不取当前元素时的最大值
int dp1 = 0; // 取当前元素时的最大值
for (int candies : currentRow) {
int newDp0 = max(dp0, dp1);
int newDp1 = dp0 + candies;
dp0 = newDp0;
dp1 = newDp1;
}
rowValues[i] = max(dp0, dp1);
}
// 在行最大值数组上再次应用 House Robber
int finalDp0 = 0;
int finalDp1 = 0;
for (int value : rowValues) {
int newFinalDp0 = max(finalDp0, finalDp1);
int newFinalDp1 = finalDp0 + value;
finalDp0 = newFinalDp0;
finalDp1 = newFinalDp1;
}
cout << max(finalDp0, finalDp1) << "\n";
}
return 0;
}
总结
本题的关键在于识别出问题可以分解为两个独立的"打家劫舍"问题:
- 行内的不相邻选择
- 行间的不相邻选择
通过这种分解,我们能够用高效的动态规划方法解决原本复杂的问题。这种"分层动态规划"的思想在解决复杂约束条件的问题时非常有用。
533

被折叠的 条评论
为什么被折叠?



