【題解】P1437 [HNOI2004] 敲磚塊
前言
模擬賽的 A,賽時基本只有我沒過。
技不如人,甘拜下風?
題意
給定一個凹槽 \(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;
}