经典问题
给定一个含有正值与负值的序列,求出其最大连续子序列
解法:维护两个端点位置first,end与此前出现过的最大和sum,与当前和s。
具体见代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
int dp[maxn];
int main(){
int n,i,j;
int sum,s;
int first,end,t;//最大连续子串和的起始及末尾
while(scanf("%d",&n)&&(n!=0)){
for(i=0;i<n;i++)
cin>>dp[i];
sum=dp[0];s=0;first=end=t=0;
for(i=0;i<n;i++){
s+=dp[i];
if(s>sum){
sum=s;
first=t;
end=i;
}
if(s<=0){
s=0;
t=i+1;
}
}
if(sum<=0) cout<<dp[0]<<" "<<dp[n-1]<<endl;
else cout<<sum<<" "<<dp[first]<<" "<<dp[end]<<endl;
}
return 0;
}
进阶问题——GSS
给定一个序列,有m个询问,每个询问给出两个端点值,问其间的最大连续子序列的最值,同时需要支持修改其中的元素
再上一个问题中,我们运用的是线性的求解方法,时间复杂度只有O(n),但是我们有另外的方法求解上面的的问题,就是分治
我们知道,一个区间的最大连续子序列是可以通过其左右两部分得到,在维护左右两个子段A,B的时候需要维护子段的三个值,分别是其最大连续子序列值maxx,从其序列左端开始的最大值lmax,从其右端开始的最大值rmax
这样做的时间复杂度为O(nlogn)
所以区间的最大子段和为
max(max(A.maxx,B.maxx),A.rmax+B.lmax)
其中A为该序列的左子端,B为该序列的右子段
所以当我们有多个询问的时候可以利用线段树,线段树的每个节点维护六个值(l,r,lmax,rmax,maxx,sum
合并操作见push_up函数
//带单点修改,详见push_up和update函数
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int A[N];
struct seg_tree{
struct node{
int l,r;
int lmax;
int rmax;
int maxx;
int sum;
}t[N<<2];
node push_up(node a,node b){
node res;
res.l=a.l;res.r=b.r;
res.lmax=max(a.lmax,a.sum+b.lmax);
res.rmax=max(b.rmax,b.sum+a.rmax);
res.maxx=max(max(a.maxx,b.maxx),a.rmax+b.lmax);
res.sum=a.sum+b.sum;
return res;
}
void build_tree(int l,int r,int rt){
t[rt].l=l;t[rt].r=r;
if(l==r){
t[rt].lmax=t[rt].rmax=t[rt].maxx=t[rt].sum=A[l];
return;
}
int m=(t[rt].l+t[rt].r)>>1;
build_tree(l,m,rt<<1);
build_tree(m,r,rt<<1|1);
t[rt]=push_up(t[rt<<1],t[rt<<1|1]);
}
void update(int idx,int k,int rt){
if(t[rt].r==t[rt].r){
t[rt].lmax=t[rt].maxx=t[rt].rmax=t[rt].sum=k;
return;
}
int mid=(t[rt].l+t[rt].r)>>1;
if(idx<=mid) update(idx,k,rt<<1);
else update(idx,k,rt<<1|1);
t[rt]=push_up(t[rt<<1],t[rt<<1|1]);
}
node query_ans(int l,int r,int rt){
if(l==t[rt].l&&t[rt].r==r){
return t[rt];
}
int m=(t[rt].l+t[rt].r)>>1;
if(r<=m) return query_ans(l,r,rt<<1);
else if(l>m) return query_ans(l,r,rt<<1|1);
else return push_up(query_ans(l,m,rt<<1),query_ans(m+1,r,rt<<1|1));
}
}tree;
int main(){
int n,q,a,b;
while(~scanf("%d",&n)){
for(int i=1;i<=n;i++) cin>>A[i];
tree.build_tree(1,n,1);
cin>>q;
while(q--){
cin>>a>>b;
cout<<tree.query_ans(a,b,1).maxx;
}
}
return 0;
}
GSS1、GSS3、GSS5大意相同
附加问题一:
每个query不仅需要输出最大连续子序列的和,还要输出其左右端点的下标
解法:在每个节点附加四个节点信息,lmax的右端点,rmax的左端点以及maxx的左右端点,维护过程也是在push_up函数中进行
附加问题二:
给定一个序列,要求两个不相邻的非空子序列使得其和最大
解法一:显然可以用GSS求解,枚举间断点(2~n-1),让左右两端在线段树上询问其最值
解法二:首先求整个序列的最大连续子序列值 M,记下first,与end位置,然后查询1 ~ first-1的最大子序列值 L,end+1 ~ n的最大子序列 R,first ~ end的最小连续子序列 X.
那么最终的答案就是
max(max(M+L,M+R),M-X);