(一维/二维)单调队列模板

Posted aemshana

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(一维/二维)单调队列模板相关的知识,希望对你有一定的参考价值。

一维单调队列

template<typename elemType,typename CMP>
struct MPQueue{
    CMP cmp;
    int Pos[1000010];
    elemType Node[1000010];
    int head,tail,limit;//limit-滑动窗口长度
    MPQueue():head(1),tail(0),limit(1){}
    MPQueue(int _limit):head(1),tail(0),limit(_limit){}
    
    bool size(){return tail-head+1;}
    bool empty(){return tail<head;}//是否为空
    elemType front_elem(){return Node[head];}//获取队首的元素
    int front_pos(){return Pos[head];}//获取队首元素在原序列中的位置
    //以上操作使用前应保证先pop_front队首的过时元素

    void set_limit(int _limit){limit=_limit;}//设置滑动窗口长度
    void clear(){head=1,tail=0;}//清空单调队列
    void push(int pos,elemType elem){//插入元素,维护最小值
        //pos-元素在原序列中的位置,elem-要插入的元素,limit-滑动窗口的长度
        while(head<=tail && (!cmp(Node[tail],elem) || pos-Pos[tail]+1>limit)) 
            --tail;
        ++tail;Node[tail]=elem;Pos[tail]=pos;
    }
    void pop_front(int pos){//处理过时的队首元素
        while(head<=tail && pos-Pos[head]>=limit) ++head;
    }
    void Query(int *Ans,int *AnsPos,int *Data,int Len){//查询每一个滑动窗口的最值
        //Ans-滑动窗口的最值,AnsPos-滑动窗口最值在原序列里的位置
        //Data-原序列,Len-原序列长度
        for(RG i=1;i<=Len;++i){
            push(i,Data[i]);
            pop_front(i);
            if(Ans!=NULL) Ans[i]=front_elem();
            if(AnsPos!=NULL) AnsPos[i]=front_pos();
        }
        return;
    }
};

一维单调队列的使用 (洛谷 P1886)

题目描述

有一个长为 (n) 的序列 (a),以及一个大小为 (k) 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

输入格式

输入一共有两行,第一行有两个正整数 (n,k)。 第二行 (n) 个整数,表示序列 (a)

输出格式

输出共两行,第一行为每次窗口滑动的最小值。
第二行为每次窗口滑动的最大值。

输入样例

8 3  
1 3 -1 -3 5 3 6 7

输出样例

-1 -3 -3 -3 3 3
3 3 5 5 6 7

Code

MPQueue<int,less<int> > Q1;//维护单调最小值的队列
MPQueue<int,greater<int> > Q2;//维护单调最大值的队列
int AnsMin[1000010],AnsMax[1000010];
int Data[1000010];
int N,K;

int main(){
    Read(N);Read(K);
    Q1.set_limit(K);Q2.set_limit(K);//设置滑动窗口长度
    for(RG i=1;i<=N;++i)
        Read(Data[i]);
    Q1.Query(AnsMin,NULL,Data,N);//我们不需要知道最值的位置,所以AnsPos设为NULL
    Q2.Query(AnsMax,NULL,Data,N);
    for(RG i=K;i<=N;++i){
        printf("%d",AnsMin[i]);
        if(i<N) printf(" ");
    }
    printf("
");
    for(RG i=K;i<=N;++i){
        printf("%d",AnsMax[i]);
        if(i<N) printf(" ");
    }
    printf("
");
    return 0;
}

二维单调队列

template<typename elemType,typename CMP>
struct MPQueue{
    CMP cmp;
    int Pos[1010];
    elemType Node[1010];
    int head,tail,limit;//limit-滑动窗口长度
    MPQueue():head(1),tail(0),limit(1){}
    MPQueue(int _limit):head(1),tail(0),limit(_limit){}
    
    bool size(){return tail-head+1;}
    bool empty(){return tail<head;}//是否为空
    elemType front_elem(){return Node[head];}//获取队首的元素
    int front_pos(){return Pos[head];}//获取队首元素在原序列中的位置
    //以上操作使用前应保证先pop_front队首的过时元素

    void set_limit(int _limit){limit=_limit;}//设置滑动窗口长度
    void clear(){head=1,tail=0;}//清空单调队列
    void push(int pos,elemType elem){//插入元素,维护最小值
        //pos-元素在原序列中的位置,elem-要插入的元素,limit-滑动窗口的长度
        while(head<=tail && (!cmp(Node[tail],elem) || pos-Pos[tail]+1>limit)
            --tail;
        ++tail;Node[tail]=elem;Pos[tail]=pos;
    }
    void pop_front(int pos){//处理过时的队首元素
        while(head<=tail && pos-Pos[head]>=limit) ++head;
    }
};

template<typename elemType,typename CMP>
struct MPQueue2D{//二维单调队列
    struct NODE{int posy;elemType Value;};
    struct CMP2{bool operator()(const NODE &A,const NODE &B)const {
        return CMP()(A.Value,B.Value);}
    };
    MPQueue<elemType,CMP> Row[1010];
    MPQueue<NODE,CMP2> Col;
    int x[1010][1010],y[1010][1010];//x[i][j]-以(i,j)为右下角的子矩阵中最值的行号
                                    //y[i][j]-以(i,j)为右下角的子矩阵中最值的列号
    elemType val[1010][1010],Data[1010][1010];
    //val[i][j]-以(i,j)为右下角的子矩阵中的最值
    //Data-二维单调队列的数据源
    int N,M,limitR,limitC;
    //N-行数,M-列数,limitR-滑动的子矩阵的行数,limitC-滑动的子矩阵的列数
    
    MPQueue2D(int _N=0,int _M=0,int _limitR=0,int _limitC=0):
        N(_N),M(_M),limitR(_limitR),limitC(_limitC) {}
    void setN(int _N){N=_N;}
    void setM(int _M){M=_M;}
    void clear(){
        for(RG i=1;i<=N;++i)
            Row[i].clear();
        Col.clear();
    }
    void set_limit(int _limitR,int _limitC){//设置滑动子矩阵的行数limitR和列数limitC
        limitR=_limitR;limitC=_limitC;
        for(RG i=1;i<=N;++i)
            Row[i].set_limit(limitC);
        Col.set_limit(limitR);
    }
    void Query(){//查询二维数组中每个滑动子矩阵的最值及其位置
        for(RG j=1;j<=M;++j){
            Col.clear();//维护列的单调队列
            for(RG i=1;i<=N;++i){
                Row[i].push(j,Data[i][j]);
                Row[i].pop_front(j);
                Col.push(i,(NODE){Row[i].front_pos(),Row[i].front_elem()});
                Col.pop_front(i);
                x[i][j]=Col.front_pos();//以(i,j)为右下角的子矩阵中最值的行号
                y[i][j]=Col.front_elem().posy;//以(i,j)为右下角的子矩阵中最值的列号
                val[i][j]=Col.front_elem().Value;//以(i,j)为右下角的子矩阵中的最值
            }
        }
    }
};

二维单调队列的使用 (洛谷 P2216,[HAOI2007]理想的正方形)

题目描述

有一个 (N imes M) 的整数组成的矩阵,现请你从中找出一个 (K imes K) 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

输入格式

第一行为3个整数,分别表示 (N,M,K) 的值

第二行至第 (N+1) 行每行为 (M) 个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。

输出格式

仅一个整数,为 (N imes M) 矩阵中所有“ (K imes K) 正方形区域中的最大整数和最小整数的差值”的最小值。

样例输入

5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2

样例输出

1

Code

MPQueue2D<int,less<int> > Q1;//维护滑动子矩阵单调最小值的二维单调队列
MPQueue2D<int,greater<int> > Q2;//维护滑动子矩阵最大值的二维单调队列
int N,M,K;

int main(){
    Read(N);Read(M);Read(K);
    Q1.N=Q2.N=N;Q1.M=Q2.M=M;
    Q1.set_limit(K,K);Q2.set_limit(K,K);//设置滑动子矩阵的行列数
    for(RG i=1;i<=N;++i)
        for(RG j=1;j<=M;++j){
            Read(Q1.Data[i][j]);//读入矩阵
            Q2.Data[i][j]=Q1.Data[i][j];
        }
    Q1.Query();Q2.Query();
    int Ans=INF;
    for(RG i=K;i<=N;++i)
        for(RG j=K;j<=M;++j)//因为子矩阵要完整,所以i,j都从K开始
            Ans=min(Ans,Q2.val[i][j]-Q1.val[i][j]);
    printf("%d
",Ans);
    return 0;
}

以上是关于(一维/二维)单调队列模板的主要内容,如果未能解决你的问题,请参考以下文章

一类需要用到单调队列的题目

[luoguP2216] [HAOI2007]理想的正方形(二维单调队列)

MangataのACM模板

背包模型

单调队列单调栈优先队列模板

单调队列模板