题意:
给定一个长度为
n
n
n的序列
a
a
a,求所有子区间的最大连续子列和之和,即
∑
i
=
1
n
∑
j
=
i
n
max
i
≤
k
≤
h
≤
j
{
∑
l
=
k
h
a
l
}
\sum_{i=1}^{n} \sum_{j=i}^{n} \max \limits_{i \le k \le h \le j} \{\sum_{l=k}^{h}a_l\}
∑i=1n∑j=ini≤k≤h≤jmax{∑l=khal}
(
1
≤
n
≤
2
×
1
0
5
,
−
1
0
9
≤
a
i
≤
1
0
9
)
(1 \le n \le 2 \times 10^5,-10^9 \le a_i \le 10^9)
(1≤n≤2×105,−109≤ai≤109)
题解:
最大连续子列和是一个经典问题,其中一个经典的
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的解法就是利用分治,将区间
[
l
,
r
]
[l,r]
[l,r]的最大连续子列和问题分解为经过分治中心,即
m
=
(
l
+
r
)
/
2
m=(l+r)/2
m=(l+r)/2的最大连续子列和,
[
l
,
m
]
[l,m]
[l,m]的最大连续子列和,
[
m
+
1
,
r
]
[m+1,r]
[m+1,r]的最大连续子列和三个子问题,其中第一个子问题的答案为
[
l
,
m
]
[l,m]
[l,m]的最大后缀和
[
m
+
1
,
r
]
[m+1,r]
[m+1,r]的最大前缀拼起来。
这道题的解法基于上述的经典问题,也是利用分治,那么我们接下来要考虑的问题也就是
[
l
,
r
]
[l,r]
[l,r]区间内跨过分治中心
m
=
(
l
+
r
)
/
2
m=(l+r)/2
m=(l+r)/2的子区间的最大子列和之和,考虑用枚举左端点,对于右端点用一些
O
(
l
o
g
n
)
O(logn)
O(logn)的手段来快速计算的思想来求解。我们先
O
(
n
)
O(n)
O(n)预处理出
w
l
wl
wl(
w
l
i
wl_i
wli为
[
i
,
m
]
[i,m]
[i,m]的最大连续子列和),
f
l
fl
fl(
f
l
i
fl_i
fli为
[
i
,
m
]
[i,m]
[i,m]的最大后缀和),
w
r
wr
wr(
w
r
i
wr_i
wri为
[
m
+
1
,
i
]
[m+1,i]
[m+1,i]的最大连续子列和),
f
r
fr
fr(
f
r
i
fr_i
fri为
[
m
+
1
,
i
]
[m+1,i]
[m+1,i]的最大前缀和)。然后枚举左端点
i
i
i,考虑右端点
j
j
j的移动过程,可以分成三个阶段:1、
w
l
i
wl_i
wli为
[
i
,
j
]
[i,j]
[i,j]的最大连续子列和;2、
f
l
i
+
f
r
j
fl_i+fr_j
fli+frj为
[
i
,
j
]
[i,j]
[i,j]的最大连续子列和;3、
w
r
j
wr_j
wrj为
[
i
,
j
]
[i,j]
[i,j]的最大连续子列和。(注意其中可能有一些阶段可以跳过)。关于上述三个阶段划分的相关证明会在后面提到。假设第一个阶段对应的右端点的区间为
[
m
+
1
,
p
]
[m+1,p]
[m+1,p],第二个阶段为
[
p
+
1
,
q
]
[p+1,q]
[p+1,q],第三个阶段为
[
q
+
1
,
r
]
[q+1,r]
[q+1,r],那么三个阶段的贡献分别为
w
l
i
×
(
p
−
m
)
,
f
l
i
×
(
q
−
p
)
+
∑
k
=
p
+
1
q
f
r
k
,
∑
k
=
q
+
1
r
w
r
k
wl_i \times (p-m),fl_i \times (q-p)+\sum_{k=p+1}^q fr_k,\sum_{k=q+1}^r wr_k
wli×(p−m),fli×(q−p)+∑k=p+1qfrk,∑k=q+1rwrk,其中
p
,
q
p,q
p,q可以通过二分得到,
∑
\sum
∑号可以通过预处理前缀和
O
(
1
)
O(1)
O(1)计算。
下面来对三个阶段的划分进行不大严谨的证明。(下面的证明不考虑缺省某个阶段的情况)
固定左端点
i
i
i以后,
f
l
i
fl_i
fli和
w
l
i
wl_i
wli看成常量,
f
r
j
fr_j
frj和
w
r
j
wr_j
wrj看成变量。
显然在右端点
j
j
j从左向右移动的过程中,
f
r
j
fr_j
frj和
w
r
j
wr_j
wrj都是不减的。一开始
f
r
j
fr_j
frj和
w
r
j
wr_j
wrj较小,且
w
l
i
>
f
l
i
wl_i>fl_i
wli>fli,所以前面是
w
l
i
wl_i
wli为
[
i
,
j
]
[i,j]
[i,j]的最大连续子列和;随着
j
j
j的向右移,
f
r
j
fr_j
frj增大,
f
l
i
+
f
r
j
>
w
l
i
fl_i+fr_j>wl_i
fli+frj>wli,所以
f
l
i
+
f
r
j
fl_i+fr_j
fli+frj作为
[
i
,
j
]
[i,j]
[i,j]的最大连续子列和。最后
w
r
j
wr_j
wrj超过
f
l
i
+
f
r
j
fl_i+fr_j
fli+frj,成为
[
i
,
j
]
[i,j]
[i,j]的最大连续子列和。为什么
w
r
j
wr_j
wrj在超过
f
l
i
+
f
r
j
fl_i+fr_j
fli+frj以后就不会出现小于
f
l
i
+
f
r
j
fl_i+fr_j
fli+frj的情况了呢?下面来证明一下
w
r
j
−
f
r
j
wr_j-fr_j
wrj−frj是随着
j
j
j的增大是不减的。
区间某个前缀的最大连续子列和区间和最大前缀区间只会有两种位置关系,即
其中黑色为最大前缀对应的区间,红色为最大连续子列和对应的区间,位置关系用反证法易证。
那么考虑在下一个位置更新最大连续子列和,那么会变成
显然所有的情况下
w
r
j
−
f
r
j
wr_j-fr_j
wrj−frj都是不减的。(具体来讲1的变化不变,2的变化增加)
这里有个坑点,因为有可能跳过阶段二,即在
w
l
i
>
f
l
i
+
f
r
j
wl_i>fl_i+fr_j
wli>fli+frj的过程中就出现了
w
r
j
>
w
l
i
wr_j>wl_i
wrj>wli的情况,所以需要用
w
l
i
−
f
l
i
wl_i-fl_i
wli−fli在
f
r
fr
fr中二分出
p
1
p_1
p1,用
w
l
i
wl_i
wli在
w
r
wr
wr中而分出
p
2
p_2
p2,那么
p
=
min
(
p
1
,
p
2
)
p=\min(p_1,p_2)
p=min(p1,p2)。
复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;
#define ll long long
#define ull unsigned long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const ll INF=1e18;
const int maxn=1e5+5;
ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n;
int a[maxn];
ll fl[maxn];//最大后缀
ll fr[maxn];//最大前缀
ll wl[maxn];//后缀最大连续子列和
ll wr[maxn];//前缀最大子列和
ll diff[maxn];//diff[i]=wr[i]-fr[i]
ull sumw[maxn],sumf[maxn];
ull ans;
void solve(int l,int r){
if(l>r)return;
if(l==r){
ans+=a[l];
return;
}
int m=(l+r)>>1;
ll suf=0;
fl[m+1]=-INF;
wl[m+1]=-INF;
for(int i=m;i>=l;i--){
suf+=a[i];
fl[i]=max(fl[i+1],suf);
if(wl[i+1]<=0){
wl[i]=a[i];
}
else{
wl[i]=wl[i+1]+a[i];
}
}
for(int i=m;i>=l;i--){
wl[i]=max(wl[i+1],wl[i]);
}
ll pre=0;
fr[m]=-INF;
wr[m]=-INF;
for(int i=m+1;i<=r;i++){
pre+=a[i];
fr[i]=max(fr[i-1],pre);
if(wr[i-1]<=0){
wr[i]=a[i];
}
else{
wr[i]=wr[i-1]+a[i];
}
}
sumw[m]=sumf[m]=0;
for(int i=m+1;i<=r;i++){
wr[i]=max(wr[i-1],wr[i]);
sumw[i]=sumw[i-1]+wr[i];
sumf[i]=sumf[i-1]+fr[i];
diff[i]=wr[i]-fr[i];
}
for(int i=l;i<=m;i++){
int p=lower_bound(fr+m+1,fr+r+1,wl[i]-fl[i])-fr;
int q=lower_bound(wr+m+1,wr+r+1,wl[i])-wr;
p=min(p,q);
if(p>m+1){
ans+=(ull)wl[i]*(p-(m+1));
}
q=lower_bound(diff+p,diff+r+1,fl[i])-diff;
if(q>p){
ans+=(ull)fl[i]*(q-p)+sumf[q-1]-sumf[p-1];
}
if(q<=r){
ans+=sumw[r]-sumw[q-1];
}
}
solve(l,m);
solve(m+1,r);
}
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
ans=0;
solve(1,n);
printf("%llu\n",ans);
return 0;
}