【题解】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;
}