RMQ-优先队列与单调队列 专题训练

优先队列

指使用STL库的priority_queue进行模拟,优点在于实现简单。可用于求区间最值,由于使用堆操作,时间复杂度在\([n\log_2{n}~n^2\log_2{n}]\),当数据较大时容易TLE

单调队列

单调队列使用STL的deque进行模拟,也可以用数组和双指针\((head,tail)\),有两种操作,删头和去尾,实现一个区间内单调增或减的队列,线性复杂度。单调队列经常与dp一起优化。

删头

判断头元素是否过期——超过窗口长度,过期即弹出;

去尾

判断尾元素是否大于新元素(递减),小于新元素(递增)。原理是若尾元素若不满足条件,则尾元素一定不可能移动到头元素,因此可以弹出;

dp优化

对一定范围内的dp数组进行单调队列维护,目的在于取一定范围内的dp最值,而不仅仅是前一个
转移方程一般为 \(dp[i]=dp[j]+w[i],(j+m<i)\)

经典题:
洛谷P1440 求m区间内的最小值

解法一:

经典ST表,MLE。需要的二维数组范围太大,超过内存限制,速度也比较慢

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define INF  0x3f3f3f3f;
#define MAXN 2000020
#define MOD 10007
#define sf(a,b) read(a),read(b)
using namespace std;
int n,m,brr[MAXN][(int)log2(MAXN)+1];
int main()
{//MLE
    scanf("%d%d",&n,&m);
    FOR2(i,1,n)
    {
        scanf("%d",&brr[i][0]);
    }
    int logm=log2(m);
    for(int h=1;h<=logm;h++)
    {
        for(int i=1;i+(1<<h)-1<=n;i++)
        {
            brr[i][h]=min(brr[i][h-1],brr[i+(1<<h-1)][h-1]);
        }
    }
    int minn=INF;
    cout<<0<<endl;
    for(int i=2;i<=n;i++)
    {

        if(i<=m+1){
            minn=min(minn,brr[i-1][0]);
            cout<<minn<<endl;
            continue;
        }
        cout<<min(brr[i-m][logm],brr[i-(1<<logm)][logm])<<endl;
    }
    return 0;
}

解法二 :

优化内存ST表,TLE,使用一层数组重复计算值,解决了内存的问题,但是ST表迭代计算速度慢的劣势导致TLE

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define INF  0x3f3f3f3f;
#define MAXN 2000020
#define MOD 10007
#define sf(a,b) read(a),read(b)
using namespace std;
int arr[MAXN],n,m,brr[MAXN];
int main()
{//TLE
    scanf("%d%d",&n,&m);
    FOR2(i,1,n)
    {
        scanf("%d",&brr[i]);
        arr[i]=brr[i];
    }
    int logm=log2(m);
    for(int h=1;h<=logm;h++)
    {
        for(int i=1;i+(1<<h)-1<=n;i++)
        {
            brr[i]=min(brr[i],brr[i+(1<<h-1)]);
        }
    }
    int minn=INF;
   printf("0\n");
    for(int i=2;i<=n;i++)
    {

        if(i<=m+1){
            minn=min(minn,arr[i-1]);
            printf("%d\n",minn);
            continue;
        }
        printf("%d\n",min(brr[i-m],brr[i-(1<<logm)]));
    }
    return 0;
}

解法三:

优先队列,550ms过,但是根堆的调整速度还是略慢

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define INF  0x3f3f3f3f;
#define MAXN 2000020
#define MOD 10007
#define sf(a,b) read(a),read(b)
using namespace std;
typedef struct{
    int val;
    int pos;
}NODE;NODE nodes[MAXN];
int n,m;
struct cmp{
    bool operator() (NODE n1,NODE n2)
    {
        return n1.val>=n2.val; //小根堆 
    }
};
priority_queue<NODE,vector<NODE>,cmp>q;
int main()
{
    scanf("%d%d",&n,&m);
    int minp=0,minn=INF;
    FOR2(i,1,n)
    {
        scanf("%d",&nodes[i].val);
        nodes[i].pos=i;
        if(i==1)cout<<0<<endl;
        else 
        {
            while(m+q.top().pos<i)q.pop();//是否过期 
            printf("%d\n",q.top().val);
        }
        if(i!=n)q.push(nodes[i]);
    }
    return 0;
}

解法四:

单调队列,400ms,线性复杂度,为正解

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define INF  0x3f3f3f3f;
#define freopenin(a) freopen(a,"r",stdin);
#define freopenout(a) freopen(a,"w",stdout);
#define MAXN 2000100
#define MOD 10007
using namespace std;
typedef struct{
    int val,pos;
}NODE;NODE nodes[MAXN];
int n,m;
deque<NODE>q;//递增 
int main()
{
    scanf("%d%d",&n,&m);
    FOR2(i,1,n)
    {
        scanf("%d",&nodes[i].val);
        nodes[i].pos=i;
        
        while(!q.empty()&&q.back().val>=nodes[i].val)q.pop_back();
        while(!q.empty()&&q.front().pos+m<=i)q.pop_front();
        q.push_back(nodes[i]);
        if(i==1) printf("0\n");
        if(i!=n)
        {
            printf("%d\n",q.front().val);
        }
        
    }
    return 0;
}

洛谷P1886 滑动窗口

解法一:

优先队列,根堆调整复杂度\(O(n\log_2{n})\)的劣势导致根本过不了第二个点,直接TLE

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define INF  0x3f3f3f3f;
#define freopenin(a) freopen(a,"r",stdin);
#define freopenout(a) freopen(a,"w",stdout);
#define MAXN 1000100
#define MOD 10007
using namespace std;
typedef struct{
    int val,pos;
}NODE;NODE nodes[MAXN];
struct cmp{
    bool operator() (NODE n1,NODE n2)
    {
        return n1.val>=n2.val;
    }
};
struct cmp2{
    bool operator() (NODE n1,NODE n2)
    {
        return n1.val<=n2.val;
    }
};
priority_queue<NODE,vector<NODE>,cmp>q;//小根堆 
priority_queue<NODE,vector<NODE>,cmp2>q2;//大根堆 
int ansx[MAXN];
int ansn[MAXN];
int n,k;
int main()
{
    cin>>n>>k;
    FOR2(i,1,n)
    {
        scanf("%d",&nodes[i].val);
        nodes[i].pos=i;
        q.push(nodes[i]);
        q2.push(nodes[i]);
        if(i-k>=0)
        {
            while(q.top().pos+k<=i)q.pop();
            while(q2.top().pos+k<=i)q2.pop();
            ansx[i-k]=q2.top().val;
            ansn[i-k]=q.top().val;
        }
    }
    FOR(i,0,n-k+1)printf("%d ",ansn[i]);
    printf("\n");
    FOR(i,0,n-k+1)printf("%d ",ansx[i]);
    return 0;
}

解法二:

单调队列,550ms过,线性复杂度NB

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define INF  0x3f3f3f3f;
#define freopenin(a) freopen(a,"r",stdin);
#define freopenout(a) freopen(a,"w",stdout);
#define MAXN 1000100
#define MOD 10007
using namespace std;
typedef struct{
    int val,pos;
}NODE;NODE nodes[MAXN];
deque<NODE>q1;//递减 
deque<NODE>q2;//递增 
int ansx[MAXN],ansn[MAXN];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    FOR(i,0,n)
    {
        scanf("%d",&nodes[i].val);
        nodes[i].pos=i;
        while(!q1.empty()&&q1.back().val>=nodes[i].val)q1.pop_back();
        while(!q2.empty()&&q2.back().val<=nodes[i].val)q2.pop_back();
        while(!q1.empty()&&q1.front().pos+m<=i)q1.pop_front();
        while(!q2.empty()&&q2.front().pos+m<=i)q2.pop_front();
        q1.push_back(nodes[i]);
        q2.push_back(nodes[i]);
        ansx[i]=q1.front().val;
//      for(int i=0;i<q1.size();i++)cout<<q1[i].val<<" ";
//      cout<<q1.size()<<endl;
//      cout<<endl;
        ansn[i]=q2.front().val;
    }
    FOR(i,m-1,n)printf("%d ",ansx[i]);
    printf("\n");
    FOR(i,m-1,n)printf("%d ",ansn[i]);
    
    return 0; 
}

JZOJ P2944烽火传递

题意

一串数字,每个窗口m内必须至少有两个点被选中,问选中点的和最小值

基本思路

使用单调队列对dp数组优化,使得当前选择的点加上之前m个状态的最小值,dp求和。
转移方程 \(dp[i]=dp[j]+w[i], (i-m<j<i)\)

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define INF  0x7f7f7f7f;
#define freopenin(a) freopen(a,"r",stdin);
#define freopenout(a) freopen(a,"w",stdout);
#define MAXN 1000100
#define MOD 10007
using namespace std;
typedef struct{
    ll val,pos;
}NODE;NODE dp[MAXN];
ll w[MAXN],n,m;
deque<NODE>q;
int main()
{
    scanf("%d%d",&n,&m);
    dp[0]={0,0};q.push_back(dp[0]);
    FOR2(i,1,n)
    {
        scanf("%d",&w[i]);
        dp[i].pos=i;
        while(!q.empty()&&q.front().pos+m<i) q.pop_front();
        dp[i].val=q.front().val+w[i];
        while(!q.empty()&&q.back().val>dp[i].val) q.pop_back();
        q.push_back(dp[i]);
    }
    ll ans=INF;
    FOR2(i,n-m+1,n)
    {
        ans=min(ans,dp[i].val);     
    }
    printf("%d\n",ans);
    return 0;
    
}

附使用数组和指针的手动单调队列,速度快20ms

#include <cstdio>
#include <cstring>
#include <algorithm>
#include<bits/stdc++.h>
using namespace std;
int n,m;
int w[100001];
int que[100001],head=0,tail=0;
int f[100001];
int main()
{
    scanf("%d%d",&n,&m);
    int i,j;
    for (i=1;i<=n;++i)
        scanf("%d",&w[i]);
    memset(f,127,sizeof f);
    f[0]=0;
    que[0]=0;
    for (i=1;i<=n;++i)
    {//对f做单调队列 
        if (que[head]<i-m)
            ++head;//将超出范围的队头删掉
        f[i]=f[que[head]]+w[i];//转移(用队头)
        while (head<=tail && f[que[tail]]>f[i])
            --tail;//将不比它优的全部删掉
        que[++tail]=i;//将它加进队尾
    }
    for(int i=0;i<=n;i++)
    {
        cout<<que[i]<<" ";
    }
    cout<<endl;
    for(int i=0;i<=n;i++)
    {
        cout<<f[i]<<" ";
    }
    cout<<endl;
    int ans=0x7f7f7f7f;
    for (i=n-m+1;i<=n;++i)
        ans=min(ans,f[i]);
    printf("%d\n",ans);
}

其他题目:
洛谷:P3957 跳房子
P2216 [HAOI2007]理想的正方形
P1725琪露诺
P1714切蛋糕
【Tyvj1305】最大最大子序和
【Vijos1243】生产产品
【Hdu3530】Subsequence
【Hdu3401】Subsequence
【Poj1742】Coins
[【Hdu4374】 One hundred layer]
【CodeForces372C】 Watching Fireworks is Fun
参考博客
https://blog.csdn.net/hjf1201/article/details/78729320
https://www.cnblogs.com/Dxy0310/p/9742045.html
https://sweetlemon.blog.luogu.org/dan-diao-dui-lie
https://blog.csdn.net/weixin_44412226/article/details/90476669

转载于:https://www.cnblogs.com/tldr/p/11269332.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值