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


    优先队列

    指使用STL库的priority_queue进行模拟,优点在于实现简单。可用于求区间最值,由于使用堆操作,时间复杂度在([nlog_2{n}~n^2log_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
    ");
        for(int i=2;i<=n;i++)
        {
    
            if(i<=m+1){
                minn=min(minn,arr[i-1]);
                printf("%d
    ",minn);
                continue;
            }
            printf("%d
    ",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
    ",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
    ");
    		if(i!=n)
    		{
    			printf("%d
    ",q.front().val);
    		}
    		
    	}
    	return 0;
    }
    

    洛谷P1886 滑动窗口

    解法一:

    优先队列,根堆调整复杂度(O(nlog_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("
    ");
    	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("
    ");
    	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
    ",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
    ",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

  • 相关阅读:
    第七章 第一月:开始 第二周:链接建设(2)
    基础篇 第四节 项目进度计划编辑 之 日历
    VSS2005 使用说明
    删除存储过程
    基础篇 第四节 项目进度计划编辑 之 任务关联性设定
    转载: input 的css技巧
    js: js判断回车键
    一个简单邮件发送类
    转载: php发送邮件原理
    css 小经验: 重构css的优化与技巧
  • 原文地址:https://www.cnblogs.com/tldr/p/11269332.html
Copyright © 2020-2023  润新知