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;
    }
    
    ------------------------------------------------------------------------------------------------------------------------
    无心插柳柳成荫才是美丽
    有哪种美好会来自于刻意
    这一生波澜壮阔或是不惊都没问题
    只愿你能够拥抱那种美丽
    ------------------------------------------------------------------------------------------------------------------------
  • 相关阅读:
    [RK3288][Android6.0] U-boot 启动流程小结【转】
    学习笔记二十三——字符函数库cctype【转】
    【Git学习笔记】用git pull取回远程仓库某个分支的更新,再与本地的指定分支自动merge【转】
    Git 少用 Pull 多用 Fetch 和 Merge 【已翻译100%】【转】
    git 拉取和获取 pull 和 fetch 区别【转】
    setprecision、fixed、showpoint的用法总结(经典!!超经典!!)【转】
    Android休眠唤醒机制简介(二)
    获取元素个数的函数
    返回两个时间范围内的一个随机时间
    全角半角转换函数
  • 原文地址:https://www.cnblogs.com/SovietPower/p/14413285.html
Copyright © 2011-2022 走看看