​ACwing 滑动窗口:​

单调对列好久之前就学了,但是一直没有深刻理解,这次又学了一下,感觉理解了点,就写篇博客记录一下。
滑动窗口这题是个经典的题,可以用线段树写,但代码量有点大,而且使用空间比较大,如果卡空间的话,显然线段树是过不去的,rmq在洛谷上好像是不能写,倍增的思想可以水过时间,但这题却过不了空间,倍增比线段树的空间大了几倍,所以这题过不去。

三种解题代码:
Rmq:ACwing 上可以

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e6;
ll n,m;
ll dma[maxn][10],dmi[maxn][10];
ll a[maxn];
void init()
{
for(ll i=1; i<=n; i++)
{
dma[i][0]=dmi[i][0]=a[i];
}
for(ll j=1; (1<<j)<=m; j++)
{
for(ll i=1; i+(1<<j)-1<=n; i++)
{
dma[i][j]=max(dma[i][j-1],dma[i+(1<<(j-1))][j-1]);
dmi[i][j]=min(dmi[i][j-1],dmi[i+(1<<(j-1))][j-1]);
}
}
}
ll rmq_max(ll l,ll r)
{
ll k=log2(r-l+1);
return max(dma[l][k],dma[r-(1<<k)+1][k]);

}
ll rmq_min(ll l,ll r)
{
ll k=log2(r-l+1);
return min(dmi[l][k],dmi[r-(1<<k)+1][k]);
}
int main()
{
scanf("%lld %lld",&n,&m);
for(ll i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
}
init();
for(ll i=1; m+i-1<=n; i++)
{
if(i+m-1!=n)
printf("%lld ",rmq_min(i,m+i-1));
else
{
printf("%lld\n",rmq_min(i,m+i-1));
}
}
for(ll i=1; m+i-1<=n;i++)
{
if(i+m-1!=n)
printf("%lld ",rmq_max(i,m+i-1));
else
{
printf("%lld\n",rmq_max(i,m+i-1));
}
}
}

线段树:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1e6+5;
int a[maxn];
struct node
{
int l,r,maxx,minx;
}tr[maxn*4];
void pushup(int k)
{
tr[k].maxx=max(tr[k<<1].maxx,tr[k<<1|1].maxx);
tr[k].minx=min(tr[k<<1].minx,tr[k<<1|1].minx);
}
void build(int k,int l,int r)
{
tr[k].l=l,tr[k].r=r;
if(l==r)
{
tr[k].minx=tr[k].maxx=a[l];
return;
}
int mid=l+r>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(k);
}
int query_m(int k,int l,int r)
{
if(tr[k].l>=l&&tr[k].r<=r)
{
return tr[k].maxx;
}
int mid=tr[k].l+tr[k].r>>1;
if(mid>=r)
{
return query_m(k<<1,l,r);
}
else if(mid<l)
{
return query_m(k<<1|1,l,r);
}
return max(query_m(k<<1,l,mid),query_m(k<<1|1,mid+1,r));
}
int query_mi(int k,int l,int r)
{
if(tr[k].l>=l&&tr[k].r<=r)
{
return tr[k].minx;
}
int mid=tr[k].l+tr[k].r>>1;
if(mid>=r)
{
return query_mi(k<<1,l,r);
}
else if(mid<l)
{
return query_mi(k<<1|1,l,r);
}
return min(query_mi(k<<1,l,mid),query_mi(k<<1|1,mid+1,r));
}
int main()
{
int n,k;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build(1,1,n);
for(int i=1;i+k-1<=n;i++)
{
if(i+k-1!=n)
{
printf("%d ",query_mi(1,i,i+k-1));
}
else
{
printf("%d\n",query_mi(1,i,i+k-1));
}
}
for(int i=1;i+k-1<=n;i++)
{
if(i+k-1!=n)
{
printf("%d ",query_m(1,i,i+k-1));
}
else
{
printf("%d\n",query_m(1,i,i+k-1));
}
}
}

单调队列:

#include<bits/stdc++.h>
using namespace std;
//单调队列
const int maxn=1e6+5;
int q[maxn],a[maxn];
int n,k;
void solv()
{
int head=0,tail=-1;
for(int i=0;i<n;i++)
{
if(head<=tail&&i-q[head]>=k)
head++;
while(head<=tail&&a[q[tail]]>a[i])
tail--;
q[++tail]=i;
if(i-k+1>=0)
cout<<a[q[head]]<<' ';
}
cout<<endl;
head=0,tail=-1;
for(int i=0;i<n;i++)
{
if(head<=tail&&i-q[head]>=k)
head++;
while(head<=tail&&a[q[tail]]<a[i])
tail--;
q[++tail]=i;
if(i-k+1>=0)
cout<<a[q[head]]<<' ';
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>k;
for(int i=0;i<n;i++)
cin>>a[i];
solv();
}

单调队列写这题的空间复杂度是O(1)的,时间复杂度是O(n)的,基本上算是这题的最优解。呢么单调队列是如和实现的呢:
单调队列一般用于解决区间最值问题,在解决这类问题中,单调队列中,存储的是下标,单调队列的头代表最值元素,呢么这一题的写法就很明显了,先判断头元素在不在合法区间,如果在就不管,不在就删去头元素即可,之后就是维护头元素操作,详细看代码