洛谷P1886 滑动窗口
题目大意:
有n个数,每次取其中的k个找最大和最小,输出n-k+1个最大值和最小值
思路:
如果单纯在每一个长度为k的区间里找最大值,时间复杂度是o(n^2)。要将时间复杂度降低到o(n),需要用到单调栈的思想,但也不完全是单调栈。以找最小值为例,维护一个栈底到栈顶单调递增的“单调栈”,栈内每个元素要有两个参数,下标idx和值v。具体操作如下:
1.若栈顶大于等于a[i],持续弹栈,直到将a[i]放在栈顶是可以维持升序的
2.若栈底的idx和i的差超过了k就是说“窗口”需要向右边滑动了,就需要将“栈底”上移。
因为有栈底上移的操作,所以这个结构不是正常的单调栈,算是单调栈的变形。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[1001000];
struct p{
ll idx,v;
}mi[2001100],ma[2001010];
int main(){
ll n,k;
ll lma = 0,lmi = 0;
ll rma = 0,rmi = 0;
scanf("%lld",&n);
scanf("%lld",&k);
for(ll i = 1 ; i <= n ; i ++){
scanf("%lld",&a[i]);
}
for(ll i = 1 ; i <= n ; i ++){
//mi升序
while(rmi > 0 && rmi >= lmi && mi[rmi].v >= a[i])rmi -- ;
mi[++rmi] = {i , a[i]};
if(i == 1 || i - mi[lmi].idx == k)lmi++;
if(i >= k)printf("%lld ",mi[lmi].v);
// cout<<"i="<<i<<" ai="<<a[i]<<" l="<<lmi << " r="<< rmi <<"\n";
// for(ll j = lmi ; j <= rmi ; j ++)
// cout<<mi[j].v <<" ";cout<<endl;
}
cout<<endl;
for(ll i = 1 ; i <= n ; i ++){
//ma降序
while(lma > 0 && rma >= lma && ma[rma].v <= a[i])rma -- ;
ma[++rma] = {i , a[i]};
if(i == 1 || i - ma[lma].idx == k)lma++;
if(i >= k)printf("%lld ",ma[lma].v);
}
}
小结:
1.写栈的时候,最好还是用数组模拟吧,毕竟可以把整个栈打出来,找错比较容易
2.边界条件的考虑很重要
3.用单调栈记录的是一个区间里的上升子序列,并且该上升子序列的第一位是该区间里的最小值