1. 程式人生 > 其它 >【題解】P1437 [HNOI2004] 敲磚塊

【題解】P1437 [HNOI2004] 敲磚塊

前言

模擬賽的 A,賽時基本只有我沒過。

技不如人,甘拜下風?

題意

P1437 [HNOI2004] 敲磚塊

給定一個凹槽 \(a\),第 \(i\) 行(列)有 \(n - i + 1\) 個值,第 \(i\) 行第 \(j\) 列的值為 \(a_{i, j}\)。限制取 \(a_{i, j}\) 前必須取 \(a_{i - 1, j}\)\(a_{i - 1, j + 1}\)。在至多取 \(m\) 個值的情況下,求取出的值的和的最大值。

\(1 \leq n \leq 50, 1 \leq m \leq \frac{n(n + 1)}{2}\)

思路

“二維xjbDP”(或 bdfs,即 Baidu-First Search)

貪心一類的亂搞顯然難過,一眼 dp。

容易想到以行劃分狀態,但是第 \(i\) 行是否能取與第 \(i - 1\) 行的狀態有關,即狀態有後效性,所以是錯的。

我們發現後效性與列有關,所以考慮以列劃分狀態。即令產生後效性的狀態被預先確定好,使其不能影響當前狀態。容易看到對於第 \(j\) 列,如果要取到第 \(i\) 行,則必須要取前 \(i - 1\) 行,並且第 \(j + 1\) 列至少要取到第 \(i - 1\) 行。

基於這種思路,令 \(dp[i][j][k]\) 表示取到第 \(i\) 列第 \(j\) 行,一共取 \(k\) 個值時的答案。根據上文,此時能轉移過來的狀態是 \([\ dp[i + 1][j - 1][k - j], dp[i + 1][n - (i + 1) + 1][k - j]\ ]\)

。所以得到狀態轉移方程:

\(dp[i][j][k] = \max_{l = j - 1}^{n - (i + 1) + 1}(dp[i + 1][l][k - j]) + \sum\limits_{k = 1}^j a_{k, i}\)

樸素 dp 時間複雜度太高,考慮優化。

\(\sum\limits_{k = 1}^j a_{k, i}\) 可以字首和預處理。\(\max_{l = j - 1}^{n - (i + 1) + 1}(dp[i + 1][l][k - j])\) 也可以後綴最大值預處理,所以時間複雜度優化成 \(O(n^2m)\)。容易發現第 \(i\) 行狀態的轉移只與第 \(i + 1\)

行的狀態有關,因此可以滾動陣列。

時間複雜度 \(O(n^2m)\),空間複雜度 \(O(nm)\)

程式碼

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 55;
const int maxm = 1.3e3 + 5;

int n, m;
int a[maxn][maxn];
int dp[2][maxn][maxm];

int main()
{
    int ans = 0;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n - i + 1; j++) scanf("%d", &a[i][j]);
    for (int i = n; i >= 1; i--)
    {
        int cur = i & 1, sum = 0;
        memset(dp[cur], 0, sizeof(dp[cur]));
        for (int j = 1; j <= n - i + 1; j++) sum += a[j][i];
        for (int j = n - i + 1; j >= 0; j--)
        {
            for (int k = max((j << 1) - 1, 0); k <= m; k++)
            {
                dp[cur][j][k] = dp[cur ^ 1][max(j - 1, 0)][k - j] + sum;
                ans = max(ans, dp[cur][j][k]);
                dp[cur][j][k] = max(dp[cur][j][k], dp[cur][j + 1][k]);
            }
            sum -= a[j][i];
        }
    }
    printf("%d\n", ans);
    return 0;
}