题目链接
题目大意
输入一个长度为n(n
≤
106
)的数列,给定一个长度为k的窗口,让这个窗口在数列上移动,求移动到每个位置窗口中包含数的最大值和最小值。即设序列为
A1
,
A2
,…,
An
,设f(i)=min{
Ai−k+1
,
Ai−k+2
,…,
Ak
} ,g(i)=max{
Ai−k+1
,
Ai−k+2
,…,
Ak
}
求:f(k),f(k+1),…,f(n) g(k),g(k+1),…,g(n).
分析
本题算是单调队列的经典入门题。因此拿来学习单调队列。
如果单纯对每一个i都去扫描前面k个数去求最值,那复杂度为o(n*k),肯定超时。
如果用线段树或者树状数组维护区间最值,那复杂度为o(nlogn)。
但还有更简洁更高效的办法,即单调队列。//可参考紫书P241
单调队列是一种特殊的队列,它能在队列两端进行删除操作,并始终维护队列保持一种单调性。
以求窗口最小值为例,我们需要维护一个内部元素值递减的队列来模拟滑动窗口,使每一次查询取队首元素即为答案。如何维护呢?有以下两种操作:
1.从队尾插入元素:当有新元素需要入队时,让它与当前队尾元素进行比较,若它小于等于当前队尾元素(即破坏了原队列的单调性),那么删除队尾元素,并继续比较队尾与新元素,直到找到一个队尾大于新元素时,将新元素插入到队尾。被删除的元素既比新元素大,又会比新元素先滑出窗口,因此肯定不会成为答案。这个操作不断维护了队列中的最值。
2.删除队首元素:由于序列中的元素当且仅当在滑动窗口时有效,因此,当队首元素不在滑动窗口内时,就删除队首元素。这个操作维护了数据的临时性。
元素是否过时与其下标有关,因此我队列中存储的是序列数对应的下标,这样每当要入队一个数,只需访问队首元素所存下标在序列中对应的数即可得到答案。
这样做尽管插入元素时有时需删除多个数,但每个数最多被删除一次,因此时间复杂度为O(n).
代码
数组模拟单调队列
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void get_min()
{
int head,tail,i;
memset(Q,0,sizeof(Q));
head=1;tail=1;
Q[tail]=1;
Min[1]=a[1];
for (i=2;i<=n;i++)
{
while ((head<=tail)&&(a[i]<a[Q[tail]]))//删除队尾元素
tail--; //插入队尾元素
Q[++tail]=i;
while ((head<=tail)&&(Q[head]<i-k+1)) //删除队首过时元素
head++;
/*由于每一层循环只添加一个数,因此每次最多删除一次队首,
故可改成if语句*/
Min[i]=a[Q[head]]; //访问答案
}
}
void get_max()
{
int head,tail,i;
memset(Q,sizeof(Q),0);
head=1;tail=1;
Q[tail]=1;
Max[1]=a[1];
for (i=2;i<=n;i++)
{
while ((head<=tail)&&(a[i]>a[Q[tail]]))
tail--;
Q[++tail]=i;
while ((head<=tail)&&(Q[head]<i-k+1))
head++;
Max[i]=a[Q[head]];
}
}
int main()
{
int i;
scanf("%d%d",&n,&k);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);
get_min();
get_max();
for (i=k;i<n;i++)
printf("%d ",Min[i]);
printf("%d\n",Min[n]);
for (i=k;i<n;i++)
printf("%d ",Max[i]);
printf("%d\n",Max[n]);
return 0;
}
使用STL中的双端队列deque容器
#include <iostream>
#include <cstdio>
#include <cstring>
#include <deque>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void Get_Min()
{
deque<int> Q;
int i;
Q.push_back(1);
Min[1]=a[1];
for (i=2;i<=n;i++)
{
while ((!Q.empty())&&(a[i]<a[Q.back()]))
Q.pop_back();
Q.push_back(i);
while ((!Q.empty())&&(Q.front()<i-k+1))
Q.pop_front();
Min[i]=a[Q.front()];
}
}
void Get_Max()
{
deque<int> Q;
int i;
Q.push_back(1);
Max[1]=a[1];
for (i=2;i<=n;i++)
{
while ((!Q.empty())&&(a[i]>a[Q.back()]))
Q.pop_back();
Q.push_back(i);
while ((!Q.empty())&&(Q.front()<i-k+1))
Q.pop_front();
Max[i]=a[Q.front()];
}
}
int main()
{
int i;
scanf("%d%d",&n,&k);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);
Get_Min();
Get_Max();
for (i=k;i<n;i++)
printf("%d ",Min[i]);
printf("%d\n",Min[n]);
for (i=k;i<n;i++)
printf("%d ",Max[i]);
printf("%d\n",Max[n]);
return 0;
}