很有味道的一场比赛,BC两个题的想法都非常精彩。
题目链接
题意
定义一个区间的贡献 f ( x ) f(x) f(x)为该区间的长度乘上该区间所有数之和的符号。问将一个序列分成若干区间的贡献之和的最大值。
题解
首先可以写出显然的
n
2
n^2
n2动态规划做法。
d
p
i
=
m
a
x
j
=
0
i
−
1
d
p
j
+
f
(
j
+
1
,
i
)
dp_i=max_{j=0}^{i-1}dp_j+f(j+1,i)
dpi=maxj=0i−1dpj+f(j+1,i)
分析区间贡献的性质,发现最优解中若
f
(
x
)
f(x)
f(x)的贡献为负,则
x
x
x的长度最多为1。可以用维护前缀最大值的树状数组或者线段树来完成这个
d
p
dp
dp的优化。
d
p
i
dp_i
dpi的初始值设为
d
p
i
−
1
+
f
(
i
,
i
)
dp_{i-1}+f(i,i)
dpi−1+f(i,i),然后用特判处理
[
1
,
i
]
[1,i]
[1,i]区间之和为正的情况,最后求
d
p
j
=
1
→
i
dp_{j=1\to i}
dpj=1→i中的最大值,维护的时候用
d
p
x
−
x
dp_x-x
dpx−x来更新即可。
注意序列的前缀和要按照大小排序更新,这样就可以通过了,应该没有什么其他问题,谢谢大家。
const int yuzu=5e5;
typedef ll fuko[yuzu|10];
fuko a,c,o,d,zw,dp;
int main() {
for (int t=read();t--;) {
int n=read(),i;
using pli=pair<ll,int>;
vector<pli> v; v.push_back({-yuzu,-yuzu});
*o=*dp=*d=0;
for (i=1;i<=n;++i) o[i]=read()+o[i-1],v.push_back(pli(o[i],-i));
sort(v.begin()+1,v.end());
auto add=[&](int x,ll v) {
for (;x<=n;x+=x&-x) zw[x]=max(zw[x],v);
};
auto query=[&](int x) {
ll ans=-1e6;
for (;x;x&=x-1) ans=max(ans,zw[x]);
return ans;
};
for (i=1;i<=n;++i) d[-v[i].second]=i,zw[i]=-1e6;
for (i=1;i<=n;++i) {
dp[i]=max(dp[i-1]+(o[i]-o[i-1]>0?1:o[i]-o[i-1]<0?-1:0),query(d[i])+i);
if (o[i]>0) dp[i]=i;
add(d[i],dp[i]-i);
}
printf("%lld\n",dp[n]);
}
}