最大连续子段和问题

经典问题

给定一个含有正值与负值的序列,求出其最大连续子序列

解法:维护两个端点位置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);

猜你喜欢

转载自blog.csdn.net/qq_41860129/article/details/91848526