牛客练习赛 小A与最大子段和 解题报告

小A与最大子段和

题意

在一个序列 \(\{a\}\) 里找到一个非空子段 \(\{b\}\), 满足 \(\sum\limits_{i=1}^{|b|}b_i\times i\) 最大

\(n\le 200000,|a_i|\le 2000\)


长的都一脸的斜率优化

考虑表示一个子段\([l+1,r]\)

\[\begin{aligned} &\sum_{i=l+1}^ra_i(i-l)\\ &=\sum_{i=l+1}^ra_i\times i-a_i\times l \end{aligned} \]

\(A_k=\sum_{i=1}^k a_ii,B_k=\sum_{i=1}^k a_i\)

那继续化简为

\[A_r-B_r\times l+B_l\times l-A_l \]

然后我们枚举\(r\),想办法搞一下\(l\)

维护一个\((i,B_i\times i-A_i)\)的上凸包,然后发现是对斜率询问最大截距,直接在凸包上三分就可以了。


Code:

#include <cstdio>
#include <algorithm>
#define ll long long
const int N=2e5+10;
using std::max;
int n,tot;
ll A[N],B[N];
struct Point
{
	ll x,y;
	Point(){}
	Point(ll X,ll Y){x=X,y=Y;}
	Point friend operator -(Point a,Point b){return Point(a.x-b.x,a.y-b.y);}
	ll cal(ll d){return d*x+y;}
}s[N];
ll Cross(Point a,Point b){return a.x*b.y-a.y*b.x;}
void ins(Point a)
{
	while(tot>1&&Cross(a-s[tot],s[tot]-s[tot-1])<=0) --tot;
	s[++tot]=a;
}
int main()
{
	scanf("%d",&n);
	ins(Point(0,0));
	ll ans=-(1ll<<50),a;
	for(int i=1;i<=n;i++)
 	{
 		scanf("%lld",&a);
 		A[i]=A[i-1]+a*i;
 		B[i]=B[i-1]+a;
 		int l=1,r=tot;
 		while(l<r)
 		{
 			int mid=l+r>>1;
 			if(s[mid].cal(-B[i])<s[mid+1].cal(-B[i])) l=mid+1;
 			else r=mid;
 		}
 		ans=max(ans,A[i]+s[l].cal(-B[i]));
 	    ins(Point(i,B[i]*i-A[i]));
 	}
 	printf("%lld\n",ans);
	return 0;
}

2019.2.16

posted @ 2019-02-16 16:24  露迭月  阅读(141)  评论(1编辑  收藏  举报