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

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

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

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

你可能感兴趣的:(队列)