Subarray Cuts
Description
给定一个长度为 的数组,你需要从中按顺序选出个不重复、不相交的子串,定义 为第 个子串的和,你需要最大化。
数据范围
Solution
我们考虑拆开绝对值,对于一段单调递增/递减的子串,结果只与其中的峰值和谷值有关,中间的数会因为或而连锁抵消掉。
于是,我们定义表示在第个数时,总共选了个子串时的状态。
其中,第三维的意义依次为:
当第三维为时,表示当前子串位于谷值;
当第三维为时,表示当前子串在谷值到峰值之间;
当第三维为时,表示当前子串位于峰值;
当第三维为时,表示当前子串在峰值到谷值之间。
注意到我们并没有,所以当或时只用一次,其余情况的这个子串同时会因前后两端的峰/谷值,要两次。
复杂度:,可以通过本题。
Code
// Author: wlzhouzhuan #pragma GCC optimize(2) #pragma GCC optimize(3) #include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long #define rint register int #define rep(i, l, r) for (rint i = l; i <= r; i++) #define per(i, l, r) for (rint i = l; i >= r; i--) #define mset(s, _) memset(s, _, sizeof(s)) #define pb push_back #define pii pair <int, int> #define mp(a, b) make_pair(a, b) inline int read() { int x = 0, neg = 1; char op = getchar(); while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); } while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); } return neg * x; } inline void print(int x) { if (x < 0) { putchar('-'); x = -x; } if (x >= 10) print(x / 10); putchar(x % 10 + '0'); } const int N = 30001, K = 201; int f[N][K][4]; int a[N], n, k; int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); } for (int j = 1; j <= k; j++) { memset(f[0][j], -0x3f, sizeof(f[0][j])); } for (int i = 1; i <= n; i++) { for (int j = 1; j <= k; j++) { int opt; if (j == 1 || j == k) opt = 1; // 贡献是1份 else opt = 2; // 贡献是2份 opt *= a[i]; f[i][j][0] = max(f[i - 1][j][0], f[i - 1][j - 1][3]) - opt; f[i][j][1] = max(f[i - 1][j][1], f[i][j][0]); f[i][j][2] = max(f[i - 1][j][2], f[i - 1][j - 1][1]) + opt; f[i][j][3] = max(f[i - 1][j][3], f[i][j][2]); if (j != 1 && j != k) { // 上一段也在峰值和谷值之间 f[i][j][1] = max(f[i][j][1], f[i - 1][j - 1][1]); f[i][j][3] = max(f[i][j][3], f[i - 1][j - 1][3]); } } } printf("%d\n", max(f[n][k][1], f[n][k][3])); return 0; }