[題解] [洛谷 P1174] 打磚塊

wxy3265發表於2024-04-20

[洛谷 P1174] 打磚塊

題目描述

\(n\)\(m\) 列的磚塊和 \(k\) 發子彈,每個磚塊都有一個得分,每次可以用一發子彈打碎某一列最下面的磚塊並得到相應的得分。有的磚塊在打碎後可以獲得一發額外子彈的獎勵。求該遊戲的最大得分。

image

輸入格式

第一行有 \(3\)個正整數, \(n,m,k\) 。表示開始的時候,有 \(n\)\(m\) 列的磚塊,小紅有 \(k\) 發子彈。

接下來有 \(n\) 行,每行的格式如下:

\(f_1 c_1 f_2 c_2 f_3 c_3 ... f_m c_m\)

其中 \(f_i\) 為正整數,表示這一行的第 \(i\) 列的磚,在打碎以後的得分。\(c_i\) 為一個字元,只有兩種可能,Y 或者 N。Y 表示有一發獎勵的子彈,N 表示沒有。

所有的數與字元之間用一個空格隔開,行末沒有多餘的空格。

輸出格式

僅一個正整數,表示最大的得分。

題解

不難看出該題需要使用動態規劃解決。設計狀態 \(dp_{i,j}\) 表示前i列,用j顆子彈能夠得到的最高分數。使用字首和 \(sum_{i,j}\) 表示打掉第 \(i\) 行第 \(j\) 列能夠得到的分數。此時不難得出狀態轉移方程: \(dp_{i,j} = max(dp[i - 1][j - l] + sum[i][l])\) 其中, \(l\) 為列舉的在當前列的花費。顯然,這種做法在不會出現 Y 時可以應對,但在出現 Y 時就會有以下問題:對於其中一列,假設是如下情況:

1 Y
1 N

那麼在打掉下面的 N 後,如果我們還剩下一顆子彈,就可以沒有花費打掉 Y ,但如果我們在打完 N 後剛好用完了所有子彈,那麼我們就無法再獲得 Y 的分數。此外還有如下特殊情況:

1 N 1 Y
1 N 1 N

這種情況下,對於第二列,我們雖然可以以一顆子彈的花費獲得兩個磚塊的分數,但是我們需要用到兩顆子彈。因此,我們需要考慮最後一顆子彈是否在這一列打完。如果最後一顆子彈在這一列打完,我們就無法透過“借用”子彈的方式獲得 Y 的分數,否則我們就可以無花費的打掉 Y 。特別的,如果我們在當前列花費的子彈數小於總子彈數,那麼我們一定可以無花費的打掉 Y 。

因此,我們需要記錄兩種狀態:\(dp1_{i,j}\) 表示最後一顆子彈打在這一列的最高分數, \(dp2_{i,j}\) 表示最後一顆子彈沒有打在這一列的分數。相應的,我們每個方塊的得分也需要分成最後一顆子彈打在這一列時每個磚塊的得分 \(sum1_{i,j}\) 和最後一顆子彈沒有打在這一列時每個磚塊的得分 \(sum2_{i, 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;
}

相關文章