如何分治求最大子段和

洛谷题目链接:最大子段和

什么是最大子段和?顾名思义,在一个序列中,找到一段,将这一段元素相加使得结果最大.

其实可以通过递推来在O(n)的时间复杂度内求出结果,也就是判断一个数前面一个数能否对最大子段和作贡献.如果前面那个数>0,那么就将它加到这一个数里,每次正在操作的数进行取max.但是这样不能在线查询(虽然这个题也不需要在线),所以我们用线段树的方法来维护每个区间的最大子段和.

线段树中记录几个变量:ls记录从区间左端点开始向右延伸能得到的最大子段和,rs记录从右端点开始向左延伸能得到的最大子段和,ss记录区间的最大子段和(不管是从区间中哪个位置开始),sum记录区间和.

我们将正在合并的区间节点编号叫root,它的左端点为l,右端点为r

那在合并ls的时候只存在这样几种情况:

  1. root左端点包含的最大子段的右端点延伸到了右儿子
  2. root左端点包含的最大子段的右端点仍然在左儿子的范围内

合并rs也是同理.
然后考虑如何合并ss,root的包含的最大子段的左端点叫x,右端点叫y,那么只有这样几种情况:

  1. \(x = l , mid+1\leq y<r\)
  2. \(x=l , y=r\)
  3. \(l<x\leq mid,mid+1\leq y<r\)
  4. \(l<x\leq mid,y=r\)
  5. \(l<x<y\leq mid\)
  6. \(mid+1\leq x<y\leq r\)

整理一下式子也就是这样:

\[ls[l,r]=max(ls[l,mid],sum[l,mid]+ls[mid+1,r]) \]

\[rs[l,r]=max(rs[mid+1,r],sum[mid+1,r]+rs[l,mid]) \]

\[ss[l,r]=max(ss[l,mid],ss[mid+1,r],ls[mid+1,r]+rs[l,mid+1]) \]

那么我们直接对这些情况进行讨论,下面看代码注释

#include<bits/stdc++.h>
#define ll(x) (x<<1)
#define rr(x) (x<<1|1)
using namespace std;
const int N=200000+5;

int n, a[N];

struct seg_tree{
	int val, l, r, ls, rs, ss, sum;
}t[N*4];

int gi(){
	int ans = 0 , f = 1; char i = getchar();
	while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
	while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
	return ans * f;
}

void up(int x){
	int lx = ll(x) , rx = rr(x);//宏定义
	int m = t[lx].rs+t[rx].ls;
	t[x].ls = max(t[lx].ls , t[lx].sum+t[rx].ls);//对区间从最左边开始维护最大连续子段
	t[x].rs = max(t[rx].rs , t[rx].sum+t[lx].rs);
	t[x].ss = max(m , max(t[lx].ss , t[rx].ss));//根据上面几种情况取最大值作为最大连续子段和
	t[x].sum = t[lx].sum + t[rx].sum;//统计区间和
}

void solve(int root, int l, int r){//递归建树
	int mid = (l+r>>1);
	t[root].l = l , t[root].r = r;
	if(l == r){
		t[root].val = t[root].sum = a[l];
		t[root].ss = t[root].ls = t[root].rs = a[l];
		return;
	}
	solve(ll(root),l,mid);
	solve(rr(root),mid+1,r);
	up(root);//向上统计答案
}

int main(){
	//freopen("data.in","r",stdin);
	n = gi();
	for(int i=1;i<=n;i++) a[i] = gi();
	solve(1,1,n);
	printf("%d\n",t[1].ss);
	return 0;
}
posted @ 2018-05-17 19:28  Brave_Cattle  阅读(279)  评论(0编辑  收藏  举报