大致题意:给定一个序列,你可以将序列划分为若干个连续非空子数组。若子数组内数字和大于0,则该子数字权值为子数组内数字个数;小于0,权值为数字个数的相反数;若等于0,权值为0.现对原序列进行划分,求所有子数组权值之和的最大值。
简易题解:观察到只有区间内数字之和大于0,才能产生贡献,考虑进行动态规划。
f[i] 表示前i位能得到的权值最大值。
求出原序列的前缀和数组p,朴素dp式如下:
for(int i = 1; i <= n; i ++) {
for(int j=0; j<i; j++)
if(p[i] - p[j] > 0) f[i] = max(f[i], f[j] + i - j);
else if(p[i] - p[j] == 0) f[i] = max(f[i], f[j]);
f[i] = max(f[i], f[i - 1] - 1);
}
可以发现,对于每个,只需要找到所有 满足 的 的 的最大值。
相当于维护前缀上的最大值,所有可以用树状数组来做。由于 的范围是级别,树状数组无法开这么大,但 的数量只有,所以可以进行离散化后在进行操作。
数组用于维护与当前前缀相等的 的最大值,因为只涉及最值单点修改和单点查询,所以直接用一个普通数组维护即可。
/*
朴素dp式:
for(int i = 1; i <= n; i ++) {
for(int j=0; j<i; j++)
if(p[i] - p[j] > 0) f[i] = max(f[i], f[j] + i - j);
else if(p[i] - p[j] == 0) f[i] = max(f[i], f[j]);
f[i] = max(f[i], f[i - 1] - 1);
}
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
#define lowbit(x) (x & -x)
#define int long long
int n;
map<int, int> id;
struct BIT {
int t[N];
inline void modify(int x, int k) {
while(x <= n) {
t[x] = max(t[x], k);
x += lowbit(x);
}
}
inline int query(int x) {
int res = -2e14;
while(x) {
res = max(t[x], res);
x -= lowbit(x);
}
return res;
}
inline clear() {
for(int i=0; i<=n; i++)
t[i] = -2e14;
}
} t1; //t1维护f[j] - j的最值
void slv() {
cin >> n;
t1.clear();
vector<int> f(n + 1, -2e14), p(n + 1, 0), t2(n + 10, -2e14), st(n + 10, 0);
for(int i = 1; i <= n; i ++) {
int x; cin >> x;
p[i] = p[i - 1] + x;
}
auto v = p;
sort(v.begin(), v.end());
for(int i=0; i<=n; i++) {
if(i == 0) {
p[i] = lower_bound(v.begin(), v.end(), p[i]) - v.begin() + 1;
f[0] = 0;
t2[p[i]] = max(0LL, t2[p[i]]);
t1.modify(p[i], 0);
}
else p[i] = lower_bound(v.begin(), v.end(), p[i]) - v.begin() + 1;
}
//for(int i=0; i<=n; i++) cout << p[i] << ' ';
for(int i = 1; i <= n; i ++) {
int ID = p[i];
f[i] = max(f[i], t1.query(ID - 1) + i);
f[i] = max(f[i], t2[ID]);
f[i] = max(f[i - 1] - 1, f[i]);
t1.modify(ID, f[i] - i);
t2[ID] = max(t2[ID], f[i]);
}
cout << f[n] << '\n';
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; cin >> _;
while(_--)
slv();
}