[COGS82] 求子序列

  这是一道经典题目了,刘汝佳在紫书上讲解了三种方法,复杂度从O(n3)->O(n2)->O(n)。

  记得高一我写这道题的时候迷迷糊糊的,对于O(n)的算法并不是很理解,今天我重新写这道题并用O(nlogn)的分治方法解决,也是为写维护数列做准备。

  divide and conquer 分而治之的思想可以说是OI中最为重要的思想方法之一了,往往比起复杂度更优的O(n)算法有着对问题有更强的应变能力,适用范围更加广的优势。

  最直观的例子比如说求一个数列在区间[l,r]内的和,我们可以O(n)预处理出前缀和并O(1)求解,但是如果加上单点修改,那么每次O(1)修改后都需要重新O(n)求前缀和,复杂度就上去了,而对于区间修改更是仅仅修改的复杂度就达到了O(n)。这时候我们就需要线段树这种基于分治思想的数据结构来支撑了。(相比较于同样log级别的树状数组来说,线段树能解决的问题范围更加广泛,也说明了分治的重要意义)。

  对于本题的分治策略,我们先考虑解对于区间[l,r]可能出现的情况。

  1:最大连续和完全在左区间[l,mid]。

  2:最大连续和完全在右区间[mid+1,r]。

  3:最大连续和跨越左右区间,由mid向左延伸,由mid+1向右延伸。

  接下来是递归终点的确定。

  显然,对于单点(区间[l,r](l==r))来说,最大连续和就是这个点代表的数值本身,连续和的起终点都为l(r)。

  之后我们就可以直接照着上面的策略写程序了。

  对于本题需要注意一些细节问题,题目对最大连续和子序列做了清晰的定义。

  1:和最大。

  2:最大和相同时起点尽量靠左。

  3:最大和及起点都相同时终点尽量靠左。

  且题目要求输出起终点,这里我用一个结构体记录最大和,起点,终点这三个信息。

 

// q.c

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int M=1000000+10;
struct Data { 
	int x,y,z; 
	bool operator > (const Data &A) {
		if(x!=A.x) return x>A.x;
		if(y!=A.y) return y<A.y;
		return z<A.z;
	}
};
int n,a[M];
Data solve(int l,int r) {
	if(l==r) return (Data){a[l],l,r};
	int mid=(l+r)>>1,lsum=a[mid],rsum=a[mid+1];
	Data lc=solve(l,mid);
	Data rc=solve(mid+1,r);
	Data lp={a[mid],mid,mid};
	Data rp={a[mid+1],mid+1,mid+1};
	for(int i=mid-1;i>=l;lsum+=a[i],i--)
		if(lsum+a[i]>=lp.x) lp.x=lsum+a[i],lp.y=i;
	for(int i=mid+2;i<=r;rsum+=a[i],i++) 
		if(rsum+a[i]>rp.x) rp.x=rsum+a[i],rp.z=i;
	Data res={lp.x+rp.x,lp.y,rp.z};
	if(lc>res) res=lc;
	if(rc>res) res=rc;
	return res;
}
int main() {
	freopen("subq.in","r",stdin);
	freopen("subq.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	Data ans=solve(1,n);
	printf("%d\n%d\n%d\n",ans.y,ans.z,ans.x);
	return 0;
}

 

posted @ 2018-04-01 17:22  qjs12  阅读(317)  评论(0编辑  收藏  举报