题目link:https://www.luogu.com.cn/problem/P1437
Part1:
首先可以考虑用第 $i$ 行来表示状态。
但是容易发现,如果想要知道第 $i$ 行的第 $j$ 个的状态,最差情况是需要枚举上面的 $2^n$ 个状态的。
因为每枚举到一个砖块,都需要考虑上面两个砖块的状态,而这两个砖块又需要分别考虑上面两个砖块的状态,以此类推。
因此不能用行来表示状态。
Part2:
这样一来,再考虑用第 $i$ 列来表示状态。
首先将这个倒三角形的形状给转化一下,例如把题目中的例子变成这样:
14 15 4 3 23 33 33 76 2 2 13 11 22 23 31
这样敲掉一个砖块的条件就变成了需要敲掉正上方的砖块和左上方的砖块。
考虑从左往右进行转移,这样容易发现如果想要敲掉第 $i$ 列的一个砖块,只需要考虑第 $i$ $-$ $1$ 列的且在这个砖块上面的砖块的状态就行了。
因此每次转移只需要考虑最多 $n$ 个状态,用列来表示状态可行。
设 $f_{i, j, k}$ 表示在第 $i$ 列从上到下打掉了 $j$ 个砖块且从第 $1$ 列到第 $i$ 列一共敲了 $k$ 个砖块的最大得分。
容易得出转移方程如下(设 $suma_{i, j}$ 表示 $a$ 数组第 $i$ 列的前缀和):
$f_{i, j, k}$ $=$ $max$${$ $f_{i - 1, p, k - j}$ $+$ $suma_{i, j}$ $}$ $(j$ $-$ $1$ $≤$ $p$ $≤$ $i$ $-$ $1);$
转移复杂度 $O(n)$,状态复杂度 $O(n^2$ $*$ $m)$,总时间复杂度 $O(n^3$ $*$ $m)$,可以考虑优化。
Part3:
发现其实 $j$ 这一维只是在找一个后缀最大值,因此在处理完每一列进行一次预处理就行了。
设 $maxf_{i, k, j}$ 表示 $max{$ $f_{i, p, k}$ $}$ $(j$ $≤$ $p$ $≤$ $i);$
当然它的转移就是这样:
$maxf_{i, k, j}$ $=$ $max($ $maxf_{i, k, j + 1}$ $,$ $f_{i, j, k}$ $);$
但是注意需要处理 $maxf_{i, k, 0}$ ,并且在处理的时候,它的转移如下:
$maxf_{i, k, 0}$ $=$ $max($ $maxf_{i - 1, k, 0}$ $,$ $maxf_{i, k, 1}$ $);$
而原因就是:
首先在用 $maxf_{i, k, 0}$ 来转移的$ $f 一定是最上层的一个砖块,而这个砖块它可以直接敲,所以并不一定需要敲掉上一列的,所以在敲掉的砖块的数量有限制的情况下,它可以选择不敲上一列的,而是去敲前 $i$ $-$ $2$ 列的砖块,因此这个递推式也就更好理解了。
Part4:
于是 $f$ 的转移方程变成这样:
$f_{i, j, k}$ $=$ $maxf_{i, k - j, j - 1}$ $+$ $suma_{i, j}$ $;$
再发现 $i$ 这一维其实是可以省略的,边循环边记录答案就行了。
总时间复杂度 $O(n^2$ $*$ $m)$。
code:
1 #include <cstdio> 2 #include <algorithm> 3 4 using std::max; 5 using std::min; 6 7 const int MAXN = 50; 8 9 int n, m, a[MAXN + 10][MAXN + 10]; 10 int suma[MAXN + 10][MAXN + 10]; 11 int f[MAXN + 10][MAXN * (MAXN + 1) + 10], maxf[MAXN * (MAXN + 1) / 2 + 10][MAXN + 10]; 12 13 int knock(int x) { 14 return x * (x + 1) / 2; 15 } 16 17 int main() { 18 19 scanf("%d %d", &n, &m); 20 for(int i = 1; i <= n; ++i) { 21 for(int j = i; j <= n; ++j) { 22 scanf("%d", &a[i][j]); 23 suma[j][i] = suma[j][i - 1] + a[i][j]; 24 } 25 } 26 27 int ans = 0; 28 for(int i = 1; i <= n; ++i) { 29 30 for(int j = 1; j <= i; ++j) { 31 for(int k = knock(j); k <= min(m, knock(i - 1) + j); ++k) { 32 f[j][k] = maxf[k - j][j - 1] + suma[i][j]; 33 if(k == m) { 34 ans = max(ans, f[j][k]); 35 } 36 } 37 } 38 39 for(int k = 1; k <= min(m, knock(i)); ++k) { 40 41 int mark; 42 for(int j = i; j >= 1; --j) { 43 if(knock(j) <= k) { 44 mark = j; 45 maxf[k][j] = f[j][k]; 46 break; 47 } 48 } 49 50 for(int j = mark - 1; j >= 1; --j) { 51 maxf[k][j] = max(maxf[k][j + 1], f[j][k]); 52 } 53 maxf[k][0] = max(maxf[k][0], maxf[k][1]); 54 55 } 56 57 } 58 59 printf("%d ", ans); 60 61 return 0; 62 }