单调队列--求几种类型的最大子段和

在解一个区间的最值问题时,我们可以用到单调队列。

单调队列维护的是区间最值。

1.最大值的维护:
     比如我们要维护一个区间为k的最大值的单调队列,由于新插入
的节点他的“生命力”肯定比原先已经在队列中的元素“活”的时间长,将插入元素不断与队尾元素比,
如果他大于队尾元素,那么r--将队尾元素删掉,(因为目前插入的这个元素值(设为pos)更大,而且“活”的时间
长,有pos在,队尾元素的有“生”之年永远都没法为最大值,故而直接无视比pos小的队尾了)。直到对空位置或者
找到了一个比pos大的队尾。
2.K区间的维护:
     比如当前k区间的起点为i,即区间为[i,i+k-1],那么如果队头元素front的下标j<i,那么front便不符合
在当前k区间范围内,那么他的值便不属于当前k区间的最值,所以f++将对头出队。这段操作绝对不会遇到队
空的情况,应为第1步已经插入了一个在区间为[i,i+k-1]的元素pos,他下标j必然符合j>=i

【代码】

struct nodes  
{
    int val,beg ;
};

nodes qu1[N];
int r1,f1 ;

void insert(int m,int id,int L)//L为区间的最左下标
{
    while(r1>=f1&&m>qu1[r1].val)
     r1--;
     qu1[++r1].val=m ;
     qu1[r1].beg=id ;
    while(qu1[f1].beg<L)
     f1++;
}

//f>r qu empty
Init: f=r=0;

insert(a[i],i,L);

poj2823是一个典型的求区间最值的问题。

下面利用单调队列求几种不同类型的最大子段和。(最小子段和也可以转换为最最大子段和,只要全部元素取反就行了)

(1)没有长度限制的最大子段和。

hdu1003 Max Sum

http://acm.hdu.edu.cn/showproblem.php?pid=1003

我们用s[i] 表示a[i]的前i项和 ,s[i]=sum(a[k])(k=1,2,.....i)

然后遍历一遍s[],记录最小的s[i]值,存于min,那么最大子段和就是 max=Max(s[i]-min);

【代码】

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;

int n;
const int maxn = 100005;
struct node
{
	int val;
	int id;
}q[maxn];
int a[maxn];
int s[maxn];

int main()
{

	int T,ca=1;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
        for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		memset(s,0,sizeof(s));
		for(int i=1;i<=n;i++)
			s[i]=s[i-1]+a[i];
		int mins=0;
		int minid=0;
		int ans=-2147483648;
		int li=0,ri=0;
		for(int i=1;i<=n;i++)
		{
            if(s[i]-mins>ans)
			{
				ans=s[i]-mins;
				ri=i;
				li=minid+1;
			}
			if(s[i]<mins)
			{
				mins=s[i];
				minid=i;
			}
		}
        printf("Case %d:\n",ca++);
		printf("%d %d %d\n",ans,li,ri);
		if(T>0)
		  printf("\n");
	}
	return 0;
}

 

(2)有上界的最大子段和。

【hdu 3415 Max Sum of Max-K-sub-sequence 】

http://acm.hdu.edu.cn/showproblem.php?pid=3415

这里的数列是一个圆,要先预处理一下,变为一条线。

这里多了一点的就是对下标有一点要求,所以我们要维护一个区间长度为k的单调队列。

假设这个区间的长度是 [i,i+k-1];

那么以i+k为结尾的,最大子段和就是 s[i+k]-q[head].(注意head的下标)

注:i+k - [i,i+k-1] <= k 

所以我们可以维护当前的k区间最小,方便求下一个节点往回长度不大于k的子段的最大值。

PS:这里的队列队头要预存一个值为sum=0的节点

【代码】

 

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
const int maxn = 200005;
int n,k;
int a[maxn];
int s[maxn];

struct node
{
	int val;
	int id;
}q[maxn];

int main()
{
    int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d %d",&n,&k);
        for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		for(int i=n+1;i<=2*n;i++)
			a[i]=a[i-n];
		memset(s,0,sizeof(s));
		for(int i=1;i<=2*n;i++)
			s[i]=s[i-1]+a[i];

		int ans=-2147483648;
		int li=0;
		int ri=0;

        int head=1;
		int tail=0;

		tail+=1;
		q[tail].val=0;
		q[tail].id=0;

		for(int i=1;i<=n+k-1;i++)
		{
			if(s[i]-q[head].val>=ans)
			{
				if(s[i]-q[head].val==ans && q[head].id+1<li)
				{
					ri=i;
					li=q[head].id+1;
				}
				else if(s[i]-q[head].val==ans && (q[head].id+1==li) && (ri-li+1>i-q[head].id))
				{
					ri=i;
					li=q[head].id+1;
				}
				else if(s[i]-q[head].val>ans)
				{	
					ans=s[i]-q[head].val;
					li=q[head].id+1;
					ri=i;
				}
			}

			while(tail>=head && q[tail].val>s[i])
				tail-=1;
			tail+=1;
			q[tail].val=s[i];
			q[tail].id=i;

			if(i>=k)
			{
				while(tail>=head && q[head].id<i-k+1)
					head+=1;
				if(q[head].id>=n)
					break;
			}
	
		}


        if(ri>n)
			ri-=n;
		printf("%d %d %d\n",ans,li,ri);
	}
	return 0;
}

 

 (3)有上下界的最大子段和

soj2680

http://soj.me/2680

这里跟第二种类型也是差不多,我们不断地加入s[]数组,维护一个单调队列。

要注意下标的限制。如果当前插入队列的是s[i],那么我们这时找到的就是以 i+ml+1为结尾的最大子段和。

注意队列头的元素的下标的出队列的问题。

 

PS:这里的队列队头要预存一个值为sum=0的节点

【代码】

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;

int n,ml,mu;
const int maxn = 40000;
int a[maxn];
int s[maxn];
struct node
{
	int val;
	int id;
}q[maxn];
int main()
{
    while(scanf("%d",&n) && n!=0)
	{
        scanf("%d %d",&ml,&mu);
        for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			a[i]=-a[i];
		}
        memset(s,0,sizeof(s));
		for(int i=1;i<=n;i++)
			s[i]=s[i-1]+a[i];

        int ans=s[ml];
        int head=1;
		int tail=0;

		tail+=1;
		q[tail].val=0;
		q[tail].id=0;


		for(int i=ml+1;i<=n;i++)
		{
			while(tail>=head && q[tail].val>s[i-ml])
				tail-=1;
			tail+=1;
			q[tail].val=s[i-ml];
			q[tail].id=i-ml;

			while(tail>=head && q[head].id<i-mu)
				head+=1;

			if(ans<s[i]-q[head].val)
				ans=s[i]-q[head].val;
		}
        printf("%d\n",-ans);
	}
	return 0;
}

 

参考自: http://hi.baidu.com/sulipol/blog/item/734f6f50ce93392a42a75b92.html

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

posted on 2011-12-16 02:51  lwbaptx  阅读(1850)  评论(0编辑  收藏  举报

导航