单调队列学习小结
单调队列::队列中元素之间的关系具有单调性,而且,队首和队尾都可以进行出队操作,只有队尾可以进行入队操作。
单调队列的常用操作如下:
(1)插入:若新元素从队尾插入后会破坏单调性,则删除队尾元素,直到插入后不再破坏单调性为止,再将其插入单调队列。(这样队列整体始终保持单调,不单调的元素会被逐个删除)
(2)获取最优(最大、最小)值:访问首尾元素。
对于调整最值问题上我们还可以通过重载运算符来进行调整。
我们可以看下面一组例子来加深对单调队列的理解:
例:
//一组数(1,3,2,1,5,6),进入单调不减队列的过程:
1入队,得到队列(1);
3入队,得到队列(1,3);
2入队,这时,队尾的的元素3>2,将3从队尾弹出,新的队尾元素1<2,不用弹出,将2入队,得到队列(1,2);
1入队,2>1,将2从队尾弹出,得到队列(1,1);
5入队,得到队列(1,1,5);
6入队,得到队列(1,1,5,6);
根据单调队列的性质,我们很容易想到其单调性能解决的之前的最长上升子序列问题:
我们可以把该序列中的元素全部放到该(单调递增)队列中,然后我们就可以得到最长的上升子序列,最后我们可以对其个数进行统计或者输出。
当然我们也可以将队列的大小进行固定,这样我们就可以得到定长的每个区间,从而可以获得该定长区间的一些性质(最大值或最小值),这样我们就可以求得区间极值。这样复杂度为o(k),相比于我们用朴素算法的枚举来看(复杂度为o(nk)),要优化很多。
我们对子序列的单调队列使用举个例子:
连续子序列的最大和 HDU3415:
Max Sum of Max-K-sub-sequence
Given a circle sequence A[1],A[2],A[3]…A[n]. Circle sequence means the left neighbour of A[1] is A[n] , and the right neighbour of A[n] is A[1].
Now your job is to calculate the max sum of a Max-K-sub-sequence. Max-K-sub-sequence means a continuous non-empty sub-sequence which length not exceed K.
Input
The first line of the input contains an integer T(1<=T<=100) which means the number of test cases.
Then T lines follow, each line starts with two integers N , K(1<=N<=100000 , 1<=K<=N), then N integers followed(all the integers are between -1000 and 1000).
Output
For each test case, you should output a line contains three integers, the Max Sum in the sequence, the start position of the sub-sequence, the end position of the sub-sequence. If there are more than one result, output the minimum start position, if still more than one , output the minimum length of them.
Sample Input
4
6 3
6 -1 2 -6 5 -5
6 4
6 -1 2 -6 5 -5
6 3
-1 2 -6 5 -5 6
6 6
-1 -1 -1 -1 -1 -1
Sample Output
7 1 3
7 1 3
7 6 2
-1 1 1
对于一个连续序列的和,这里可以用前缀和来处理,i —— j 的和就是sum [ j ] - sum [ i - 1 ];(如果成环的话我们可以算前缀和的时候算到2n)
对于同一个序列终点来说(sum[j]确定),起点左边那个元素的前缀和越小,所得到的和越大。所以我们解决问题的关键就是找出[i,j]区间内的一点x使得sum[x]最小。我们结合单调队列:
#include <stdio.h>
#include <limits.h>
const int M = 100001<<1;
int sum[M],q[M];
int main()
{
int z,n,k;
scanf("%d",&z);
while(z--)
{
int start,end,max;
int head,rear;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&sum[i]);
sum[i+n] = sum[i];
}
for(int i=2;i< n+k;i++)
sum[i] += sum[i-1];
head = rear = 0;
q[head] = 0;
max = INT_MIN;
for(int i=1;i< n+k;i++) //枚举每个区间终点
{
while(head<=rear && sum[i-1]<=sum[ q[rear] ])
rear--;
q[++rear] = i-1;
if(q[head]+1 < i-k+1) //起点如果大于区间范围,删除队首
head++;
if(sum[i] - sum[ q[head] ] > max)
{
start = q[head]+1;
end = i;
max = sum[i] - sum[ q[head] ];
}
}
end = end>n ? end%n : end;
printf("%d %d %d\n",max,start,end);
}
return 0;
}