[洛谷 P1174] 打砖块
题目描述
有 n n n 行 m m m 列的砖块和 k k k 发子弹,每个砖块都有一个得分,每次可以用一发子弹打碎某一列最下面的砖块并得到响应的得分。有的砖块在打碎后可以获得一发额外子弹的奖励。求该游戏的最大得分。
输入格式
第一行有 3 3 3个正整数, n , m , k n,m,k n,m,k 。表示开始的时候,有 n n n 行 m m m 列的砖块,小红有 k k k 发子弹。
接下来有 n n n 行,每行的格式如下:
f 1 c 1 f 2 c 2 f 3 c 3 . . . f m c m f_1 c_1 f_2 c_2 f_3 c_3 ... f_m c_m f1c1f2c2f3c3...fmcm
其中 f i f_i fi 为正整数,表示这一行的第 i i i 列的砖,在打碎以后的得分。 c i c_i ci 为一个字符,只有两种可能,Y 或者 N。Y 表示有一发奖励的子弹,N 表示没有。
所有的数与字符之间用一个空格隔开,行末没有多余的空格。
输出格式
仅一个正整数,表示最大的得分。
题解
不难看出该题是动态规划。设计状态 d p i , j dp_{i,j} dpi,j 表示前i列,用j颗子弹能够得到的最高分数。使用前缀和 s u m i , j sum_{i,j} sumi,j 表示打掉第 i i i 行第 j j j 列能够得到的分数。此时不难得出状态转移方程: d p i , j = m a x ( d p [ i − 1 ] [ j − l ] + s u m [ i ] [ l ] ) dp_{i,j} = max(dp[i - 1][j - l] + sum[i][l]) dpi,j=max(dp[i−1][j−l]+sum[i][l]) 其中, l l l 为枚举的在当前列的花费。显然,这种做法在不会出现 Y 时可以应对,但在出现 Y 时就会有以下问题:对于其中一列,假设是如下情况:
1 Y
1 N
那么在打掉下面的 N 后,如果我们还剩下一颗子弹,就可以没有花费打掉 Y ,但如果我们在打完 N 后刚好用完了所有子弹,那么我们就无法再获得 Y 的分数。此外还有如下特殊情况:
1 N 1 Y
1 N 1 N
这种情况下,对于第二列,我们虽然可以以一颗子弹的花费获得两个砖块的分数,但是我们需要用到两颗子弹。因此,我们需要考虑最后一颗子弹是否在这一列打完。如果最后一颗子弹在这一列打完,我们就无法通过“借用”子弹的方式获得 Y 的分数,否则我们就可以无花费的打掉 Y 。特别的,如果我们在当前列花费的子弹数小于总子弹数,那么我们一定可以无花费的打掉 Y 。
因此,我们需要记录两种状态: d p 1 i , j dp1_{i,j} dp1i,j 表示最后一颗子弹打在这一列的最高分数, d p 2 i , j dp2_{i,j} dp2i,j 表示最后一颗子弹没有打在这一列的分数。相应的,我们每个方块的得分也需要分成最后一颗子弹打在这一列时每个砖块的得分 s u m 1 i , j sum1_{i,j} sum1i,j 和最后一颗子弹没有打在这一列时每个砖块的得分 s u m 2 i , j sum2_{i, j} sum2i,j 。同时对两个状态进行转移即可。
AC代码
#include <algorithm>
#include <iostream>
#define int long long
using namespace std;
const int MAXN = 1003;
int f[MAXN][MAXN];
char c[MAXN][MAXN];
int dp1[MAXN][MAXN]; //dp_i,j: 考虑前i列,花费j颗子弹,最后一颗子弹打在这一列的最高分数
int dp2[MAXN][MAXN]; //dp_i,j: 考虑前i列,花费j颗子弹,最后一颗子弹不打在这一列的最高分数
int sum1[MAXN][MAXN], sum2[MAXN][MAXN]; // sum:分数的前缀和
int n, m, k;
signed main() {
// 数据输入
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> f[i][j] >> c[i][j];
}
}
// 初始化sum
for (int i = 1; i <= m; i++) {
int cnt = 0;
for (int j = n; j > 0; j--) {
if (c[j][i] == 'Y') {
sum1[i][cnt] += f[j][i]; // 这一列可以预支
} else {
cnt++;
sum1[i][cnt] = sum2[i][cnt] = sum1[i][cnt - 1] + f[j][i];
}
}
}
// dp
for (int j = 1; j <= m; j++) { // 列数
for (int i = 0; i <= k; i++) { // 消耗的总子弹数
for (int l = 0; l <= min(n, i); l++) { // 在当前列消耗的子弹数
dp1[j][i] = max(dp1[j][i], dp1[j - 1][i - l] + sum1[j][l]); // 如果当前这一列不是最后打到的,就可以无脑预支
if (l != 0) dp2[j][i] = max(dp2[j][i], dp1[j - 1][i - l] + sum2[j][l]); // 不预支的情况
if (l < i) dp2[j][i] = max(dp2[j][i], dp2[j - 1][i - l] + sum1[j][l]); // 一定可以预支的情况
}
}
}
cout << dp2[m][k] << '\n';
return 0;
}