2020ICPC小米邀请赛决赛B Rikka with Maximum Segment Sum

题目链接

题意:

给定一个长度为 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=1nj=inikhjmax{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) (1n2×105,109ai109)

题解:

最大连续子列和是一个经典问题,其中一个经典的 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×(pm),fli×(qp)+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 wrjfrj是随着 j j j的增大是不减的。
区间某个前缀的最大连续子列和区间和最大前缀区间只会有两种位置关系,即
在这里插入图片描述
其中黑色为最大前缀对应的区间,红色为最大连续子列和对应的区间,位置关系用反证法易证。
那么考虑在下一个位置更新最大连续子列和,那么会变成
在这里插入图片描述
显然所有的情况下 w r j − f r j wr_j-fr_j wrjfrj都是不减的。(具体来讲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 wlifli 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值