博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

小米邀请赛 决赛. B. Rikka with Maximum Segment Sum(分治 决策单调性)

题目链接


\(Description\)
\(mss(A)\)为序列A的最大子段和,求\(\sum_{1\leq i\leq j\leq n}mss([A_i,...,A_j])\)
\(n\leq 10^5\)

\(Solution\)
是不是能够用线段树做,也就是可以区间合并做的,都可以考虑过不过\(mid\)的答案啊(也就是分治)。
想到分治就容易点了(现场果然歪榜,看都没看这题)。

对于求最大子段和的过程,考虑分治。
考虑过\(mid\)的区间\([l,r]\),答案要么是\([l,mid]\)中最大的一段,要么是\([mid+1,r]\)中最大的一段,要么是\([l',mid]\)\([mid+1,r']\)的两段和。且\(r\)向右过程中答案会从情况一变成情况三再变成情况二。
第一种直接算。
第二种容易发现,最优解的取值在若干个点\(p_i\),且越往右最优解\(f(p_i)=\text{mss}(mid+1,p_i)\)越大,也就是\(r\)\([p_{i-1}+1,p_i]\)中时第二种情况的答案就是\(f(p_i)\)
第三种同理,\(\max_{l'}[l',mid]\)直接求,\([mid+1,r']\)最优解的取值也是若干个点\(p_i\),越往右\(g(p_i)=\text{sum}(mid+1,p_i)\)越大,\(r\)\([p_{i-1}+1,p_i]\)中答案为\(g(p_i)\)
\(l\)从右往左的过程中第一种情况的贡献是单增的,所以有更多位置的\(r\)选择第一种情况做答案而不选择二三。同理情况二贡献单减,选择三位置的\(r\)会更多。所以可以用单调性或二分求以二三情况作为最优解的\(r\)区间,求出来算一下个数即可。
(没必要找出最优的那些\(p_i\),令\(f(i)=\max\{f(i),f(i-1)\},\ g(i)=\max\{g(i),g(i-1)\}\)然后就可以单调性/二分位置)
复杂度\(O(n\log n)\)\(O(n\log^2n)\)。(题解是树状数组我人傻了)

思路很简单,细节可能有点多?比着写了一个还是挺有意思的。


//25ms	2444kb
#include <bits/stdc++.h>
#define pc putchar
#define gc() getchar()
#define pb push_back
typedef long long LL;
typedef unsigned long long ull;
const int N=1e5+5,INF=1e9+10;

int A[N];
LL f[N],g[N];
ull Ans;

inline int read()
{
	int now=0,f=1; char c=gc();
	for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
	for(;isdigit(c);now=now*10+c-48,c=gc());
	return now*f;
}
void Solve(const int l,const int r)
{
	if(l>r) return;
	if(l==r) {Ans+=A[l]; return;}
	int m=l+r>>1;
//R //这样用代码块写感觉还行啊 
	{
		LL sum=0,mxMR=-INF,now=0,ansR=-INF;
		for(int i=m+1; i<=r; ++i)//f:与mid相连的mss  g:不需与mid相连的mss 
		{
			sum+=A[i], mxMR=std::max(sum,mxMR);
			now=std::max(now,0ll)+A[i], ansR=std::max(ansR,now);
			f[i]=mxMR, g[i]=ansR;//f:f g:d
		}
	}
//L
	{
		LL sum=0,mxLM=-INF,now=0,ansL=-INF;
		for(int i=m; i>=l; --i)
		{
			sum+=A[i], mxLM=std::max(sum,mxLM);
			now=std::max(now,0ll)+A[i], ansL=std::max(ansL,now);
			f[i]=mxLM, g[i]=ansL;
		}
	}
	ull now=0,res=0;//ull!
	for(int i=l; i<=m; ++i) res+=g[i];//[l~mid,mid]
	for(int i=m+1; i<=r; ++i) now+=g[i];

	int p1=m+1,p2=m+1,cnt1=0,cnt2=0;
	//[mid+1,p1):g[i]*cnt1  [p1,p2):(f[i]+f[r])*cnt2  [p2,r]:g[r]
	//g[p] -> f[i]+f[p] -> g[i]
	//左边的f[i],g[i]需要用cnt维护(与当前i有关),右边的用now维护(与i无关) 
	for(int i=m; i>=l; --i)
	{
		while(p2<=r && g[p2]<f[i]+f[p2]) now+=f[p2]-g[p2], ++p2, ++cnt2;
		while(p1<p2 && f[i]+f[p1]<g[i]) now-=f[p1], ++p1, --cnt2, ++cnt1;
		while(p2<=r && p1==p2 && g[p2]<g[i]) now-=g[p2], ++p1, ++p2, ++cnt1;
		res+=now+g[i]*cnt1+f[i]*cnt2;
	}
	Ans+=res;
	Solve(l,m-1), Solve(m+1,r);
}

int main()
{
	const int n=read();
	for(int i=1; i<=n; ++i) A[i]=read();
	Solve(1,n), printf("%llu\n",Ans);

	return 0;
}
posted @ 2021-02-18 19:53  SovietPower  阅读(140)  评论(0编辑  收藏  举报