题解 P5424 【[USACO19OPEN]Snakes】

· · 题解

首先有一个非常显然的分段式DP:

f[i][j]表示前i条蛇,改变j次大小所得到的最小剩余空间

那么可以得到如下转移方程: $f[i][j] = min(f[i][j], f[k][j-1]+g[k+1][i]) 那么考虑怎么求$g[i][j]$。 根据定义,一段区间的空余空间一定是用区间中的最大值作为网的大小,减去其他数字得到的和。即$g[i][j]=maxn*(j-i+1)-sum(i,j)$,其中区间和可以用前缀和$O(1)$处理。 由于我们的$dp$已经是$O(n^3)$了,所以我们考虑快速(最好是$O(1)$)求出区间最大值。 首先想到的是线段树,但有一只$log$,太慢了。然后发现没有修改操作,可以用$st$表$O(1)$查询搞一搞(没写过不知道效果怎么样)。 但实际上有一种更简洁的方式,由于我们枚举断点的时候会从$i-1$向下逐一枚举,那么我们可以在枚举断点的时候同时更新最大值。 ```cpp #include <bits/stdc++.h> #define MAX 405 using namespace std; int n, m; int a[MAX], f[MAX][MAX], s[MAX]; int main() { cin >> n >> m; for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); s[i] = s[i-1]+a[i]; } m++; memset(f, 0x3f, sizeof(f)); f[0][0] = 0; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= min(m, i); ++j) { int mx = a[i]; for (int k = i-1; k >= 0; --k) { f[i][j] = min(f[i][j], f[k][j-1]+mx*(i-k)-(s[i]-s[k])); mx = max(mx, a[k]); } } } int ans = 0x3f3f3f3f; for (int i = 0; i <= m; ++i) { ans = min(ans, f[n][i]); } cout << ans << endl; return 0; } ```