zoukankan      html  css  js  c++  java
  • 小米邀请赛 决赛. B. Rikka with Maximum Segment Sum(分治 决策单调性)

    题目链接


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

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

    对于求最大子段和的过程,考虑分治。
    考虑过(mid)的区间([l,r]),答案要么是([l,mid])中最大的一段,要么是([mid+1,r])中最大的一段,要么是([l',mid])([mid+1,r'])的两段和。且(r)向右过程中答案会从情况一变成情况三再变成情况二。
    第一种直接算。
    第二种容易发现,最优解的取值在若干个点(p_i),且越往右最优解(f(p_i)= ext{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)= ext{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(nlog n))(O(nlog^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
    ",Ans);
    
    	return 0;
    }
    
    ------------------------------------------------------------------------------------------------------------------------
    无心插柳柳成荫才是美丽
    有哪种美好会来自于刻意
    这一生波澜壮阔或是不惊都没问题
    只愿你能够拥抱那种美丽
    ------------------------------------------------------------------------------------------------------------------------
  • 相关阅读:
    Anroid自定义RatingBar,显示半个(小数个)的stepSize时,变为整数个的问题
    浅谈Java对象回收的三种方式
    今天为火狐社区做了点小小的共享,开心一下~~
    解决mac的日历问题:服务器响应一个错误
    android.content.res.Resources$NotFoundException:String resource ID #ffffffff
    Android 读写文件的第一种方式(文件方式)
    ListView的性能提升
    C++中的回调函数实现,sink方式
    技巧:Activity收集器
    技巧:知晓当前在哪个Activity
  • 原文地址:https://www.cnblogs.com/SovietPower/p/14413285.html
Copyright © 2011-2022 走看看