在解一个区间的最值问题时,我们可以用到单调队列。
单调队列维护的是区间最值。
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
这里跟第二种类型也是差不多,我们不断地加入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