题目描述
难度分:2000
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105),k(0≤k≤min(20,n)),x(−109≤x≤109)和长为n的数组a(−109≤a[i]≤109)。
你需要把a中恰好k个数增加x,其余数减少x。该操作必须恰好执行一次。
在最优操作下,a的最大连续子数组和的最大值是多少?注意子数组可以是空的,元素和为0。
输入样例
4
4 1 2
2 -1 2 3
2 2 3
-1 2
3 0 5
3 2 4
6 2 -8
4 -1 9 -3 7 -8
输出样例
5
7
0
44
算法
前后缀分解+动态规划
注意到k≤20,如果比较敏感的话可以很容易想到一个线性DP
。以某个a[i]为基准,分别考虑前缀数组[1,i]有多少个数是加上x的,后缀数组[i+1,n]有多少个数是加上x的。
状态定义
f1[i][j]表示前缀数组[1,i]中有j个数加上x的情况下,以a[i]结尾的子数组最大累加和。f2[i][j]表示前缀数组[i,n]中有j个数加上x的情况下,以a[i]开头的子数组最大累加和。
状态转移
分别考虑a[i]到底是加x还是减x,先考虑前缀:
- 如果i≥j,说明前缀数组[1,i−1]中可以有j−1个数加x,若a[i]加上x,则有状态转移方程f1[i][j]=max(f1[i−1][j−1],0)+a[i]+x。
- 如果i−1≥j,说明前缀数组[1,i−1]中就可以有j个数加x,当前的a[i]就不用加x了,需要减x,此时有状态转移方程max(0,f1[i−1][j])+a[i]−x。
同理,后缀也是一样考虑:
- 如果n−i≥j−1,说明后缀数组[i+1,n]中可以有j−1个数加x,若a[i]加上x,则有状态转移方程f1[i][j]=max(f2[i+1][j−1],0)+a[i]+x。
- 如果n−i≥j,说明后缀数组[i+1,n]中就可以有j个数加x,当前的a[i]就不用加x了,需要减x,此时有状态转移方程max(0,f2[i+1][j])+a[i]−x。
前后缀的动态规划做完之后,就开始枚举i∈[1,n],对于每个i,枚举前缀[1,i]中加x的数的个数left,那么后缀[i+1,n]中加x的个数就是right=k−left。维护f1[i][left]+max(0,f2[i+1][right])的最大值即可得到答案,后面之所以要和0取个max,就是因为f2[i+1][right]有可能是负贡献,可以不要,此时直接以a[i]结尾即可。
复杂度分析
时间复杂度
瓶颈在于动态规划的过程,状态数量为O(nk),单次转移的时间复杂度为O(1),因此算法的时间复杂度为O(nk)。
空间复杂度
瓶颈在于前后缀两个DP
数组,不包括原始输入的数组a,仅开辟了有限几个变量,因此额外空间复杂度为O(nk)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int T, n, k, x;
LL a[N], f1[N][21], f2[N][21];
int main() {
scanf("%d", &T);
for(int cases = 1; cases <= T; cases++) {
scanf("%d%d%d", &n, &k, &x);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
for(int j = 0; j <= k; j++) {
f1[i][j] = f2[i][j] = -INF;
}
}
f1[0][0] = f2[n + 1][0] = 0;
for(int j = 1; j <= k; j++) {
f1[0][j] = -INF;
f2[n + 1][j] = -INF;
}
for(int i = 1; i <= n; i++) {
f1[i][0] = max(f1[i - 1][0] + a[i] - x, a[i] - x);
int j = n - i + 1;
f2[j][0] = max(f2[j + 1][0] + a[j] - x, a[j] - x);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= min(i, k); j++) {
if(i - 1 >= j) f1[i][j] = max(0LL, f1[i - 1][j]) + a[i] - x;
if(i >= j) f1[i][j] = max(f1[i][j], max(0LL, f1[i - 1][j - 1]) + a[i] + x);
}
}
for(int i = n; i >= 1; i--) {
for(int j = 1; j <= min(n - i + 1, k); j++) {
if(n - i >= j) f2[i][j] = max(0LL, f2[i + 1][j]) + a[i] - x;
if(n - i >= j - 1) f2[i][j] = max(f2[i][j], max(0LL, f2[i + 1][j - 1]) + a[i] + x);
}
}
LL ans = 0;
for(int i = 1; i <= n; i++) {
for(int left = 0; left <= min(i, k); left++) {
int right = k - left;
if(n - i >= right) {
ans = max(ans, f1[i][left] + max(0LL, f2[i + 1][right]));
}
}
}
printf("%lld\n", ans);
}
return 0;
}