zoukankan      html  css  js  c++  java
  • 队列学习笔记

    队列

    认识一下

    队列,英文名是 queue , 先进入队列的元素一定先出队列,因此队列通常也被称为先进先出(first in first out)表,简称 FIFO 表

    基操这里(写栈的时候顺便了)


    双端队列

    双端队列是指一个可以在 队首/队尾 插入或删除元素的队列。相当于是栈与队列功能的结合。具体地,双端队列支持的操作有 4 个

    1. 在队首插入一个元素
    2. 在队尾插入一个元素
    3. 在队首删除一个元素
    4. 在队尾删除一个元素

    用法 ↓

    #include<deque>
    deque<Type> d;
    deque<Type> d(d1); 复制一个deque
    d[i] : 返回d中下标为i的元素
    d.front() : 返回第一个元素
    d.back() : 返回最后一个元素
    d.pop_back() : 删除尾部的元素 不返回值
    d.pop_front() :删除头部元素 不返回值
    d.push_back(e) : 在队尾添加一个元素e
    d.push_front(e) : 在队头添加一个元素e
    d.clear() : 清空所有元素
    d.empty() : 空则返回1 不空返回0
    d.insert(pos, num) : 在pos位置插入元素num
    d.erase(pos) : 删除pos位置的元素 
      ...
    

    deque是双向开口的连续性存储空间,与vector类似,不同的是deque支持在队首插入元素,而且在空间存储上也比vector更优,,,

    就是常数大 还有不少函数就不举出了,想看更多用法及代码的点这里


    数组模拟队列

    我们可以用 数组 方便地模拟个队列 ↓

    • int q[SIZE], ql = 1, qr; 用两个变量标记队尾队首
    • q[++qr] = x; 插入元素
    • ++ql; 删除元素
    • q[ql] / q[qr] 访问队首队尾
    • ql = 1, qr = 0; 清空队列

    数组模拟双端队列同理


    循环队列

    随时间推移,会出现 假溢出 (实际上有空闲位置但发生了上溢),

    解决方法 : 将数组下标为 (0) 的位置看作是最后一个位置的后继 (x 的后继为 (x + 1) % size)


    双栈模拟队列

    有点冷门

    使用两个栈 F,S 模拟一个队列, 其中 F 是队尾的栈, S 代表队首的栈, 支持 push (在队尾插入),pop (在队首弹出)

    1. Push : 插入到栈 F
    2. Pop : 如果 S 非空,让 S 弹栈;否则把 F 的元素倒过来圧到 S 中(其实就是一个一个弹出插入,做完后是首位颠倒的),然后再让 S 弹栈

    易证,每个元素只会进入/转移/弹出一次,均摊复杂度 (O(1))

    双栈模拟双端队列的栗题

    一个双端队列(deque),m 个事件:

    1. 在前端插入 (w,v)
    2. 在后端插入 (w,v)
    3. 删除前端的二元组
    4. 删除后端的二元组
    5. 给定 l,r,在当前 deque 中选择一个子集 S 使得 (sumlimits_{(w,v)in S} w~mod~pin[l,r]) ,且最大化 (sumlimits_{(w,v)in S}v)

    因为本人又菜又蒻所以就没有写..

    单调队列

    概念

    元素递增或递减,只能从队首或队尾进行操作

    一个栗题说明问题 ↓

    滑稽窗口

    维护两次队列,分别求最大值和最小值

    #include <bits/stdc++.h>
    using namespace std;
    long long n, k, o[1000009], q[1000009], qq[1000009];
    
    void miin(){
        int h = 1, t = 0;
        for(int i = 1; i <= n; i++){
            while(h <= t && q[h] + k <= i) h++;
            while(h <= t && o[i] < o[q[t]]) t--;
            q[++t] = i;
            if(i >= k) cout << o[q[h]] << " ";
        }
    }
    
    void maax(){
        int h = 1, t = 0; 
        for(int i = 1; i <= n; i++){
            while(h <= t && qq[h] + k <= i) h++;
            while(h <= t && o[i] > o[qq[t]]) t--;
            qq[++t] = i;
            if(i >= k) cout << o[qq[h]] << " ";
        }
    }
    
    int main(){
        cin >> n >> k;
        for(int i = 1; i <= n; i++) cin >> o[i];
        miin();
        cout << "
    ";
        maax();
        return 0;
    }
    
    

    P1714 切蛋糕

    板子?,,维护一个单调队列,及时把过期的蛋糕和没用的蛋糕清除(负的还不如不吃

    #include <bits/stdc++.h>
    using namespace std;
    int n, m, a, ans, o[500009];
    deque<int> q;
    
    int Max(int x, int y){ return x > y ? x : y; }
    
    int main(){
    	std::ios::sync_with_stdio(0); 
        cin >> n >> m;
        for(int i = 1; i <= n; i++) {
            cin >> a;
            o[i] = a + o[i - 1];
        } 
        for(int i = 1; i <= n; i++){
            while(q.size() and o[q.back()] > o[i]) q.pop_back();//确保队首最大
    	q.push_back(i); 
            while(q.front() < i - m) q.pop_front();//过期清理
            ans = max(ans, o[q.back()] - o[q.front()]);     
        }
        cout << ans;
        return 0;
    }
    

    栗题

    为了更好地理解生日礼物这道题,先来一道逛画展

    P1638 逛画展

    题目大意 : 给出一排画,找到一个最短区间,包含所有画师画的画

    看到数据范围 N<=1000000,想到用 (O(n)) 的做法,遍历这 n 个点,遇到的点都放到队列中,若和队首相同则把队首弹出,更新 ans

    #include <bits/stdc++.h>
    using namespace std;
    
    int n, m, tot, l, r, len =  1000009, o[1000009], a[2009];
    deque<int> q;
    
    int main(){
        cin >> n >> m;
        for(int i = 1; i <= n; i++) cin >> o[i];
        for(int i = 1; i <= n; i++) {
            if(!a[o[i]]) tot ++;
            a[o[i]] ++;
            q.push_back(i);
            while(q.size() and a[o[q.front()]] > 1){
                a[o[q.front()]] --;
                q.pop_front();
            }
            if(tot == m){
                if(len > q.size()){
                    len = q.size();
                    l = q.front();
                    r = q.back();
                }
            }
        }
        cout << l << " " << r;
        return 0;
    }
    

    P2564 [SCOI2009]生日礼物

    题目大意 :一条彩带,找到一个最短区间,包含所有彩珠种类,一个位置可以放多个彩球

    看到数据范围 N <= 1000000,我们考虑单调队列。因为彩珠分种类还得记录位置,考虑开个结构体,存颜色和位置,按位置排序。因为截取的是连续一段,所以队尾不弹出,而队首的弹出条件是:当这个颜色出现次数大于等于2。这个开桶记录一下就可以,然后用一个变量 tot 记录当前队列有几种颜色,当 tot = k 时,更新答案(队尾位置-队首位置)

    //STL版
    #include <bits/stdc++.h>
    using namespace std;
    
    int n, k, tot, ans = 2147483647, cnt, _, __, a[70];
    struct hh{
        int sp, pos;
    } o[1000009];
    deque<hh> q;
    
    bool cmp(hh a, hh b){ return a.pos < b.pos; }
    
    int main(){
        std::ios::sync_with_stdio(0);
        cin >> n >> k;
        for(int i = 1; i <= k; i++){
            cin >> _;
            for(int j = 1; j <= _; j++){
                cin >> __;
                o[++cnt].pos = __, o[cnt].sp = i;
            }
        }
        sort(o + 1, o + n + 1, cmp);
        for(int i = 1; i <= n; i++){
            q.push_back(o[i]);
            a[o[i].sp] ++;
            if(a[o[i].sp] == 1) tot ++;
            while(tot == k){
                hh l = q.front(), r = q.back();
                if(ans > r.pos - l.pos) ans = r.pos - l.pos;
                q.pop_front();
                a[l.sp] --;
                if(!a[l.sp]) tot --;
            }
        }
        cout << ans;
        return 0;
    }
    
    #include <bits/stdc++.h>
    #define N 1000010
    using namespace std;
    typedef long long ll;
    inline int in() {
        int x = 0, f = 1; char C = getchar();
        while(C < '0' or C > '9') { if(C == '-') f = -1; C = getchar(); }
        while(C >= '0' and C <= '9') x = (x << 3) + (x << 1) + (C ^ 48), C = getchar();
        return x * f;
    }
    int n, k, h = 1, t, q[N], orz[100], ans = 0x7fffffff;
    struct hh { int co, pos; } o[N];
    bool cmp(hh x, hh y) { return x.pos < y.pos; }
    
    int main() {
        n = in(), k = in();
        for(int i = 1, cnt, id = 0; i <= k; i ++) {
            cnt = in();
            for(int j = 1; j <= cnt; j ++) 
                o[++ id].co = i, o[id].pos = in();
        }
        sort(o + 1, o + n + 1, cmp);
        for(int i = 1, tot = 0; i <= n; i ++) {
            if(!orz[o[i].co]) tot ++;
            q[++ t] = i; orz[o[i].co] ++;
            while(h <= t and orz[o[q[h]].co] > 1) orz[o[q[h]].co] --, h ++;
            if(tot == k) ans = min(ans, o[q[t]].pos - o[q[h]].pos);
        }
        cout << ans;
        return 0;
    }
    

    P2569 [SCOI2010]股票交易

    题目大意 : zcl 通过肉眼不可见的观察,预测到了某支股票未来T天的走势,在第 i 天,买入每股花 (AP_i) ,一次最多买 (AS_i) 股,卖出每股得 (BP_i) ,((AP_i ≥ BP_i)) ,一次最多卖 (BS_i) 股,由于某种原♂因,两次交易至少间隔 (W) 天(第 (i) 天交易,第 (i+W+1) 天才能再次交易,买入/卖出均算一次交易, 由于 zcl 还要找妹子, 他最多把精力放在 (MaxP) 支股票上,,在第 (0) 天,zcl 幻想手里有一笔巨款,想要赚到最多的钱,他给自己设计了一个程序,但是忘了保存,他想让你帮他还原这个程序...

    不难想到本题可以用动态规划来解,队列&DP

    数据范围 (0≤T≤2000) ,二维数组开得起

    第一个维度,用来表示是第几天

    第二个维度,记录拥有多少张股票

    得:(f[i][j]) 表示第 $i $ 天后拥有 (j) 张股票可以赚到的最多钱数

    分情况讨论 :

    (i) 天,

    1. 买入 a. 从0开始 b.前一状态转移而来
    2. 卖出
    3. 不买卖

    不难发现,一共4种情况,其中1a的情况是独立的(没有转移)

    1 . 买入,0基础

    仅本情况下的状态可以直接赋值,其他状态初值为 -inf,即:

    [f[i,j] = -ap_i imes j ]

    2 . 不买卖

    直接由上一天转移,且拥有股票数量不变

    [f[i][j]=max(f[i][j],~f[i-1][j]) ]

    3 . 买入,在之前的基础上

    假设在第 (i) 天交易了,下次交易最早在 (i+W+1) 天,但是也可以在第 (i+W+2) 天交易,,所以 (f[i][j]) 不一定是哪天转移来的(鸽了不止 (W) 天的情况),但是情况2已经考虑到了所以不用担心(。-ω-)

    现在要求 (f[i][j]),若第 (i-W-1) 天有 (k) 张股票,因为现在要买入,所以 (j) 是大于 (k) 的,考虑到 zcl 单次购买是有上限的,所以 (k) 最小等于 (j-as_i) ,→ ((j-as_i⩽k<j)) ,然后本次交易买了 (j-k) 张股票,花费 ((j-k)*ap_i) ,所以转移方程为

    [f[i][j]=max[f[i][j],~f[i-W-1][k]-(j-k) imes ap_i] ]

    4 .卖出

    卖完之后股票变少了,所以 (j<k⩽j+bp_i) ,卖出所得为 ((k-j) imes bp_i),所以转移方程为

    [f[i][j]=max(f[i][j],f[i-W-1][k]+(k-j) imes bp_i) ]

    dp部分结束 ❀❀✿

    对于3,4,我们发现时间复杂度是 (O(n^3)) qaq,

    img

    全剧终❀❀

    正题来了 ↓ ↓ ↓

    队列优化dp

    利用队列的单调性,我们可以把ta优化成时间复杂度为 (O(n^2))

    转移方程为

    [f[i][j]=max(f[i][j],~f[i-W-1][k]-(j-k) imes ap_i)(j-as_i⩽k<j) ]

    乘法分配率

    [f[i][j]=max(f[i][j],f[i-W-1][k]-j imes ap_i+k imes ap_i)(j-as_i⩽k<j) ]

    现要转移给 (f[i][j]),因为我们要找的是区间 (j-as_i⩽k<j) 内的最大值,而变量是 (k),所以可以把 (j imes ap_i) 提出来,发现 (f[i-W-1][k]+k imes ap_i) 可以单调性优化,也就是求一个区间中的最大值,这不就是滑稽♂窗口嘛

    然后对于情况3,正序,对于情况4,逆序, 。

    #include<bits/stdc++.h>
    using namespace std;
    int n, m, w, ap, as, bp, bs, ans, q[2010], f[2010][2010];
    
    int main() {
    	cin >> n >> m >> w;
    	memset(f, 128, sizeof f);
    	for(int i = 1; i <= n; i ++) {
    		cin >> ap >> bp >> as >> bs;
    		for(int j = 0; j <= as; j ++) f[i][j] = -1 * j * ap;
    		for(int j = 0; j <= m; j ++) f[i][j] = max(f[i][j], f[i - 1][j]);
    		if(i <= w) continue;
    		
    		for(int j = 0, h = 1, t = 0; j <= m; j ++) {
    			while(h <= t and q[h] < j - as) h ++;
    			while(h <= t and f[i - w - 1][q[t]] + q[t] * ap <= f[i - w - 1][j] + j * ap) t --;
    			q[++ t] = j;
    			if(h <= t) f[i][j] = max(f[i][j], f[i - w - 1][q[h]] + q[h] * ap - j * ap);
    		}
    		for(int j = m, h = 1, t = 0; j >= 0; j --) {
    			while(h <= t and q[h] > j + bs) h ++;
    			while(h <= t and f[i - w - 1][q[t]] + q[t] * bp <= f[i - w - 1][j] + j * bp) t --;
    			q[++ t] = j;
    			if(h <= t) f[i][j] = max(f[i][j], f[i - w - 1][q[h]] + q[h] * bp - j * bp);
    		}
    	}
    	for(int i = 0; i <= m; i ++) ans = max(ans, f[n][i]);
    	cout << ans;
    	return 0;
    }
    

    优先队列

    认识一下

    (priority\_queue) ←就是这东西,搁 (STL) 里的 ,你还在手打堆吗 ,包含了队列所拥有的所有特性和基操, 只是在队列的基础上添加了内部的一个排序,本质是由二叉堆来实现的,每次插入一个数据都是插入到数据数组的最后一个位置上,然后再做上浮操作,,,

    基操

    #include<queue>
    priority_queue<Type, Container, Functional>
    //Type为数据类型,Container为容器类型 比如vector deque(list除外),Functional为比较的方式
    priority_queue<int, vector<int>, greater<int> > q;
    priotity_queue<int, vector<int>, less<int> > q;
    //greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)→此为引用,我也没看懂(划掉)
    q.top(); //访问队头元素
    q.empty(); //队列是否为空
    q.size(); //返回元素个数
    q.push(); //插入元素到队尾并排序
    q.emplace(); //原地构造一个元素并插入队列
    q.pop(); //弹出队头元素
    q.swap(); //交换内容
    

    行了行了认识一下得了
    全剧终❀✿✿

    而我们终其一生,都希望能成为更好的人。
  • 相关阅读:
    第四次实验报告
    第三次实验报告
    第五章 循环结构课后反思
    第二次实验报告
    第一次实验报告
    第一次作业
    第九章实验报告(构造数据类型)
    第八章实验报告(指针实验)
    第七章实验报告(数组实验)
    第六章 函数和宏定义实验(2)
  • 原文地址:https://www.cnblogs.com/moziii/p/13340621.html
Copyright © 2011-2022 走看看