题目描述
难度分:1300
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的数组a(−109≤a[i]≤109)。
执行如下操作,直到a中只剩下1个数:
- 删除a[i],如果a[i]左右两边都有数字,则把a[i−1]和a[i+1]合并成一个数。
输出最后剩下的那个数的最大值。
输入样例
3
6
-3 1 4 -1 5 -9
5
998244353 998244353 998244353 998244353 998244353
1
-2718
输出样例
9
2994733059
-2718
算法
动态规划
这个题乍一看感觉很贪心或者并查集,但是最后发现DP
可以做。由于每次删除一个数之后,合并的是两个下标奇偶性相同的数,所以本质上这个题目就是在求一个子序列,子序列中每个数在原数组中的下标之差都是偶数的情况下,子序列的最大和。
状态定义
dp[i]表示保留a[i],且以a[i]结尾的序列中的最大和,在这个定义下maxi∈[1,n]dp[i]就是答案。
状态转移
当遍历到a[i]时,它可以接在一个子序列后面,这个子序列的结尾是一个下标j<i且奇偶性和i相同的数,所以状态转移方程为dp[i]=max(a[i],dp[j]+a[i]),其中j<i且j与i奇偶性相同。为了O(1)进行状态转移,我们就可以维护一个长度为2的数组ma,ma[0]表示所有j<i的偶数下标dp[j]的最大值,ma[1]表示所有j<i的偶数下标dp[j]的最大值,可以一边DP
一边维护。
复杂度分析
时间复杂度
状态数量为O(n),单次转移为O(1),因此时间复杂度为O(n)。
空间复杂度
除去输入的数组a,空间消耗的瓶颈就是DP
数组。即动态规划的状态数量,因此整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
const LL INF = 1e15;
int T, n, a[N];
LL dp[N], ma[2];
void solve() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
if(n == 1) {
printf("%d\n", a[1]);
return;
}
LL ans = a[1];
dp[1] = a[1];
ma[0] = ma[1] = -INF;
ma[1] = max(ma[1], 1LL*a[1]);
for(int i = 2; i <= n; i++) {
dp[i] = max(1LL*a[i], ma[i&1] + a[i]);
ma[i&1] = max(ma[i&1], dp[i]);
ans = max(ans, dp[i]);
}
printf("%lld\n", ans);
}
int main() {
scanf("%d", &T);
while(T--) {
solve();
}
return 0;
}