单调队列或单调栈的学习及认识

单调队列或单调栈的学习及认识

。。。顾名思义,数据是具有某方面的单调性质的(单增或单减等)。单调队列一般是用于优化动态规划方面问题的一种特殊数据结构,且多数情况是与定长连续子区间问题相关联。

一、数据结构的认识

1.双端队列deque

双端队列是一种线性表,遵守先进先出的原则。其支持下面四种操作:
(1)从队首删除
(2)从队尾删除
(3)从队尾插入
(4)查询线性表中任一元素的值

2.单调队列

单调队列是种特殊双端队列,其内部元素具有单调性。最常用的是最大队列与最小队列,其内部元素分别单增或单减。
有如下操作:
(1)插入:若新元素从队尾插入会破坏单调性,则删除队尾元素,直到不在破坏单调性为止,再将其加入单调队列尾。
(2)获取最优(max,min)值:访问队首元素。

二、例题

1.自愿者选拔

题目大意:有一群自愿者陆陆续续的来排队,队首的人在某些时刻才会离开,每个人有一个int型的人品值,现在就问在当前队列中人品最大的是多少,没有的话输出-1.

1.C name rp 名字为name人品为rp的人排到队尾
2.G 队首的人出队
3.Q 询问当前队列中rp最高的

分析:这个就是一个连续区间最大值的问题。这时单调队列里面的人出了先后顺序是单增的,其rp是单减的(head to tail)。用一个out变量记录目前出队了多少人,in记录进来了多少了,
当某次询问时,如果out <= in时,显然输出-1,不然就输出队首的人的rp值。
因为队列里面做调整的时候,对首的人原位置并不是第一,所以要记录些这个每个人的原位置。这样就可以O(n)的解决这个问题了。

const int maxn = 1e6 + 10;
struct node {
    char *name;//名字
    int rp;//rp值
    int pos;//原队列中的位置
    node() {}
    node(char c[], int rp,int pos) {
        this->name = c;
        this->rp = rp;
        this->pos = pos;
    } 
}p[maxn];
int main(int argc, const char * argv[])
{   
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    int t;
    cin >> t;
    char name[10];
    int rp;
    while(t--) {
        char op[10];
        int head = 0, tail = 0;
        int out = 0;
        int pos = 0;
        while(scanf("%s", op) != EOF) {
            if (strcmp(op, "START") == 0) continue;
            if (op[0] == 'C') {
            //这里做插入,在先后顺序不受影响的情况下,删除rp值下的
                scanf("%s %d", name, &rp);
                while(head < tail && p[tail-1].rp < rp) tail--;
                p[tail++] = node(name, rp, ++pos);
            }else if (op[0] == 'G') {
            //这个地方做删除,原队列中是删除了,但是单调队中是否
            //删除对手元素必须观察你在原队列中的所属位置
            //并且删除的值队后面的询问不会产生影响的
                out++;
                if (p[head].pos == out) head++;
            }else if (op[0] == 'Q'){
                if (head == tail) printf("-1\n");
                else printf("%d\n", p[head].rp);
            }else if (op[0] == 'E') break;
        }
    }
    return 0;
}

2.Sliding Windows

题目大意:给定n,k值和n个数字,问数列中从左到右每连续k个数的最大最小值。
分析:这又是一个定长区间的最值问题。对于区间[L, R]和[L + 1, R + 1],当上一个区间访问完后,删掉a[L]是不会对后面区间的访问产生影响的,这时位置和值都呈现出单调性了。当访问R时,对于队首的元素,如果不在[L,R]范围内的话就剔除队列,对于尾部元素则是小于a[R]的剔除队列。

const int maxn = 1e6 + 10;
int n, m;
struct node {
    int pos, value;
    node() {}
    node(int pos,int value) {
        this->pos = pos;
        this->value = value;
    }
}p[maxn];
int a[maxn];
void get_max() {
    int head = 0, tail = 0;
    for (int i = 1;i <= n;++i) {
        while(head < tail && i - p[head].pos >= m) head++;
        while(head < tail && a[i] > p[tail - 1].value) tail--;
        p[tail++] = node(i,a[i]);
        if (i >= m) printf("%d%c", p[head].value, i == n?'\n':' ');
    }
}
void get_min() {
    int head = 0, tail = 0;
    for (int i = 1;i <= n;++i) {
        while(head < tail && i - p[head].pos >= m) head++;
        while(head < tail && a[i] < p[tail - 1].value) tail--;
        p[tail++] = node(i, a[i]);
        if (i >= m) printf("%d%c", p[head].value, i == n?'\n':' ');
    }
}
int main(int argc, const char * argv[])
{   
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    while(scanf("%d %d", &n, &m) != EOF) {
        for (int i = 1;i <= n;++i)
            scanf("%d", &a[i]);
        get_min();
        get_max();
    }
    return 0;
}

3.Max Sum of Max-K-sub-sequence

题目大意:给出一个循环数列a[1]…a[n],a[1]与a[n]相邻。然后问数列中连续不超过k个元素的最大和是多少并打印出Maxsum始末位置。
分析:这题先得变换下表达方式,a[j+1] + … + a[i] = sum[i] - sum[j],sum[i]表示a[1]到a[i]的和。对于给定的i而言,ans = max(sum[i] - sum[j]) && (i - j <= k - 1),这时sum[i]是个定值,求ans的最大等同于求sum[j]的最小值i - k <= j <= i。就相当于维护定长区间的最小值问题了,每次更新答案的时候记录下始末位置就好了。

#include <stdio.h>
#include <stdlib.h>
const int maxn = 1e5 + 10;
const int oo = -2099900000;
int q[maxn<<1];
int s[maxn<<1];
int nu[maxn<<1];
int n, k;
int main(int argc, const char * argv[])
{   
    int t;
    scanf("%d", &t);
    int i;
    int ans, first, second;
    int head, tail;
    while(t--) {
        scanf("%d %d", &n, &k);
        s[0] = 0;
        for (i = 1;i <= n;++i) {
            scanf("%d", &nu[i]);
            s[i] = s[i-1] + nu[i];
        }
        for (i = n + 1;i <= n + k - 1;++i)
            s[i] = s[i - 1] + nu[i - n];
        head = 0, tail = 0;
        ans = oo;
        for (i = 1;i <= n + k - 1;++i) {
            //对与i而言,我们需要把其会用到的状态先加入到队列
            //s[i-1],队列中的元素值,必须是当前考虑状态能用到的
            while(head < tail && s[i - 1] < s[q[tail - 1]]) tail--;
            while(head < tail && i - q[head] > k) head++;
            q[tail++] = i - 1;
            if (s[i] - s[q[head]] > ans) {
                ans = s[i] - s[q[head]];
                first = q[head] + 1;
                second = i;
            }
        }
        if (second > n) second -= n;
        printf("%d %d %d\n", ans, first, second);
    }
    return 0;
}

你可能感兴趣的:(单调队列)