牛客练习赛 小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

猜你喜欢

转载自www.cnblogs.com/butterflydew/p/10388170.html