单调队列:ACwing滑动窗口

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值