如何分治求最大子段和
洛谷题目链接:最大子段和
什么是最大子段和?顾名思义,在一个序列中,找到一段,将这一段元素相加使得结果最大.
其实可以通过递推来在O(n)的时间复杂度内求出结果,也就是判断一个数前面一个数能否对最大子段和作贡献.如果前面那个数>0,那么就将它加到这一个数里,每次正在操作的数进行取max.但是这样不能在线查询(虽然这个题也不需要在线),所以我们用线段树的方法来维护每个区间的最大子段和.
线段树中记录几个变量:ls记录从区间左端点开始向右延伸能得到的最大子段和,rs记录从右端点开始向左延伸能得到的最大子段和,ss记录区间的最大子段和(不管是从区间中哪个位置开始),sum记录区间和.
我们将正在合并的区间节点编号叫root,它的左端点为l,右端点为r
那在合并ls的时候只存在这样几种情况:
- root左端点包含的最大子段的右端点延伸到了右儿子
- root左端点包含的最大子段的右端点仍然在左儿子的范围内
合并rs也是同理.
然后考虑如何合并ss,root的包含的最大子段的左端点叫x,右端点叫y,那么只有这样几种情况:
- \(x = l , mid+1\leq y<r\)
- \(x=l , y=r\)
- \(l<x\leq mid,mid+1\leq y<r\)
- \(l<x\leq mid,y=r\)
- \(l<x<y\leq mid\)
- \(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;
}