问题背景
小M最近沉迷于一款弹子游戏机,该游戏机的版面由钉子和得分点组成。玩家的目标是通过放置弹珠来获得尽可能多的分数。当弹珠碰到钉子时,它会随机向左下或右下移动;如果当前格子为空或者为得分点,则弹珠直接向下掉落。本题要求计算在一个给定版面中,玩家可能获得的最大分数。
问题描述
输入
- 版面的高
N和宽M。 - 接下来
N行,每行包含M个整数,表示版面上每个位置的状态:-1代表钉子。0代表空白。- 非负整数代表得分点,经过时可以获得对应的分数。
输出
- 玩家可以得到的最大分数。
数据范围
- 对于30%的数据,
1 <= N, M <= 10。 - 对于50%的数据,
1 <= N, M <= 100。 - 对于100%的数据,
1 <= N, M <= 1000,且-1 <= x <= 100。
解决方案分析
动态规划方法
考虑到弹珠的移动方向受到钉子的影响,我们可以采用动态规划的方法从底部向上递推地解决问题。具体步骤如下:
- 初始化:创建一个与输入版面同样大小的二维数组
dp,用于存储从当前位置到底部能够获取的最大分数。 - 设置边界条件:对于最后一行,直接将得分点的值赋给对应位置的
dp值。 - 状态转移:从倒数第二行开始向上遍历,对于每个位置,根据其是否为钉子以及处于边界的特殊情况来决定状态转移方程。
- 求解结果:遍历
dp数组的第一行,找到最大值即为最终答案。
C++代码实现
#include <iostream>
#include <vector>
#include <algorithm> // For std::max
int solution(int n, int m, std::vector<std::vector<int>>& board) {
// 创建dp数组,记录从每个位置到底部能获取的最大分数
std::vector<std::vector<int>> dp(n, std::vector<int>(m, 0));
// 初始化最后一行
for (int j = 0; j < m; ++j) {
if (board[n-1][j] >= 0) { // 如果是得分点
dp[n-1][j] = board[n-1][j];
}
}
// 从倒数第二行向上遍历
for (int i = n - 2; i >= 0; --i) {
for (int j = 0; j < m; ++j) {
if (board[i][j] == -1) { // 当前位置是钉子
// 根据边界情况处理
if (j == 0) {
dp[i][j] = dp[i+1][j+1];
} else if (j == m-1) {
dp[i][j] = dp[i+1][j-1];
} else {
dp[i][j] = std::max(dp[i+1][j-1], dp[i+1][j+1]);
}
} else { // 不是钉子
int score = (board[i][j] > 0) ? board[i][j] : 0;
dp[i][j] = dp[i+1][j] + score; // 加上正下方的得分
}
}
}
// 返回第一行中的最大值
return *std::max_element(dp[0].begin(), dp[0].end());
}
int main() {
std::vector<std::vector<int>> array1 = {{-1, 0, -1}, {100, 0, 0}, {0, 50, 70}};
std::vector<std::vector<int>> array2 = {{-1, 0, -1}, {0, -1, 0}, {50, 100, 70}, {80, 200, 50}};
std::cout << (solution(3, 3, array1) == 50) << std::endl;
std::cout << (solution(4, 3, array2) == 130) << std::endl;
return 0;
}
性能分析
该算法的时间复杂度为O(NM),其中N是版面的高度,M是宽度。空间复杂度同样是O(NM),因为我们需要一个额外的二维数组来存储中间结果。这在题目给定的数据范围内是可以接受的。
通过上述分析和代码实现,我们解决了弹子游戏机得分最大化的问题,并保证了算法的有效性和效率。