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

来源:互联网 发布:俄国构成主义知乎 编辑:程序博客网 时间:2024/04/28 00:03

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

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

一、数据结构的认识

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;}
0 0
原创粉丝点击