算法竞赛进阶指南——队列学习笔记
https://flowus.cn/xjsc01/share/395ca9dc-315c-4bd5-a942-016709980c03
这里面有我个人内容的系统整理
队列和他的变种:
- 普通队列
- 双端队列
- 优先队列(小根堆, 大跟堆)
习题AcWing132. 小组队列
我采用一个队列,表示小组的总的排行,然后使用小组个数个队列来保存每一个组里面的队列。
通过我这样一波操作,可以在队列的队列里面进行Push和Pop
代码
#include <bits/stdc++.h> using namespace std; int cnt = 1; void Solve(int n) { queue<int> total; bool inqueue[1005] = {false}; vector<queue<int > >g(1005);//每一组的队列 printf("Scenario #%d\n", cnt); map<int , int>mp; { for(int i = 1; i <= n; i++) { int num; scanf("%d", &num); for(int j = 1; j <= num; j++) { int buf; scanf("%d", &buf); mp[buf] = i; } } } char input[12]; scanf("%s", input); while(input[0] != 'S') { int id; scanf("%d", &id); if(input[0] == 'E') { if(!inqueue[mp[id]]) { inqueue[mp[id]] = true; total.push(mp[id]); } g[mp[id]].push(id); } else if(input[0] == 'D') { printf("%d\n", g[total.front()].front()); g[total.front()].pop(); if(g[total.front()].empty()) { inqueue[total.front()] = false; total.pop(); } } scanf("%s", input); } printf("\n"); } int main() { int n; while(scanf("%d", &n) && n) {Solve(n);cnt++;} return 0; }
ACWing蚯蚓133
蛐蛐国最近蚯蚓成灾了!
隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓。
蛐蛐国里现在共有 n
只蚯蚓,第 i 只蚯蚓的长度为 a**i ,所有蚯蚓的长度都是非负整数,即可能存在长度为 0
的蚯蚓。
每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只,将其切成两段。
若有多只最长的,则任选一只。
神刀手切开蚯蚓的位置由有理数 p
决定。
一只长度为 x
的蚯蚓会被切成两只长度分别为 ⌊p**x⌋ 和 x−⌊p**x⌋
的蚯蚓。
特殊地,如果这两个数的其中一个等于 0
,则这个长度为 0
的蚯蚓也会被保留。
此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加一个非负整数 q
。
蛐蛐国王知道这样不是长久之计,因为蚯蚓不仅会越来越多,还会越来越长。
蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要 m
秒才能到来。
蛐蛐国王希望知道这 m
秒内的战况。
具体来说,他希望知道:
- m
秒内,每一秒被切断的蚯蚓被切断前的长度,共有 m
个数。
m
秒后,所有蚯蚓的长度,共有 n+m
- 个数。
输入格式
第一行包含六个整数 n,m,q,u,v,t
,其中:n,m,q 的意义参考题目描述;u,v,t 均为正整数;你需要自己计算 p=u/v(保证 0<u<v);t
是输出参数,其含义将会在输出格式中解释。
第二行包含 n
个非负整数,为 a1,a2,…,a**n,即初始时 n
只蚯蚓的长度。
同一行中相邻的两个数之间,恰好用一个空格隔开。
输出格式
第一行输出 ⌊m/t⌋
个整数,按时间顺序,依次输出第 t 秒,第 2t 秒,第 3t
秒,……被切断蚯蚓(在被切断前)的长度。
第二行输出 ⌊(n+m)/t⌋
个整数,输出 m 秒后蚯蚓的长度;需要按从大到小的顺序,依次输出排名第 t,第 2t,第 3t
,……的长度。
同一行中相邻的两个数之间,恰好用一个空格隔开。
即使某一行没有任何数需要输出,你也应输出一个空行。
请阅读样例来更好地理解这个格式。
数据范围
1≤n≤105
,
0≤a**i≤108,
0<p<1,
0≤q≤200,
0≤m≤7∗106,
0<u<v≤109,
1≤t≤71输入样例:
3 7 1 1 3 1 3 3 2 输出样例:
3 4 4 4 5 5 6 6 6 6 5 5 4 4 3 2 2 样例解释
样例中,在神刀手到来前:3只蚯蚓的长度为 3,3,2。
1秒后:一只长度为 3 的蚯蚓被切成了两只长度分别为 1 和 2 的蚯蚓,其余蚯蚓的长度增加了 1。最终 4 只蚯蚓的长度分别为 (1,2),4,3。 括号表示这个位置刚刚有一只蚯蚓被切断。
2秒后:一只长度为 4 的蚯蚓被切成了 1 和 3。5 只蚯蚓的长度分别为:2,3,(1,3),4。
3 秒后:一只长度为 4 的蚯蚓被切断。6 只蚯蚓的长度分别为:3,4,2,4,(1,3)。
4秒后:一只长度为 4 的蚯蚓被切断。7 只蚯蚓的长度分别为:4,(1,3),3,5,2,4。
5秒后:一只长度为 5 的蚯蚓被切断。8 只蚯蚓的长度分别为:5,2,4,4,(1,4),3,5。
6秒后:一只长度为 5 的蚯蚓被切断。9 只蚯蚓的长度分别为:(1,4),3,5,5,2,5,4,6。
7秒后:一只长度为 6 的蚯蚓被切断。10 只蚯蚓的长度分别为:2,5,4,6,6,3,6,5,(2,4)。
所以,7秒内被切断的蚯蚓的长度依次为 3,4,4,4,5,5,6。
7秒后,所有蚯蚓长度从大到小排序为 6,6,6,5,5,4,4,3,2,2。
-
在这道题目,蚯蚓的长度是随着时间在进行着变化,可以知道,长度可以表达成y-t平面内的一个直线。斜率已知,使用每一只蚯蚓的初始长度来表达蚯蚓的长度(这样到最后输出时以及在比较过程中统一,好比较)。
-
这道题目可以使用优先队列来进行,但是时间复杂度是 m l o g 2 n mlog_2n mlog2n 所以不太可能实现。
-
这时候就需要进行理性的思考,需要使用线性的时间来进行求解。
-
归纳总结一个问题:
两类排序的比较:
- 完全排一遍,然后找到规律,可以是线性。
- 一直使用优先队列进行求解,时间复杂度是mlongn
- 一直使用排序(不推荐)
这道题目中找规律:
注意:q>=0 ,并且0<p<1。
假设我第一遍已经把最初的排序排好了,然后从中取出一个最大的,然后切成两端,放回去。这样就没有利用到我最初排好的序。
PS:如果我发现把一个东西插入到线性的队列里面很复杂,那么就多开几个队列。
代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; queue<ll>que; queue<ll>que1; queue<ll>que2; ll buf[200006]; ll n,m,q,u,v,di; ll t;//这个时间是动态变化的 void Divide(ll x, ll &t1, ll &t2) { t1 = x * u / v; t2 = x - t1; } ll getnow(ll b, ll tt) { return b + q * tt; } ll getb(ll now, ll tt) { return now - tt * q; } void Process(ll x) { x = getnow(x, t-1); ll t1 = x * u / v; ll t2 = x - t1; t1 = getb(t1, t); t2 = getb(t2, t); que1.push(t1); que2.push(t2); } int main() { scanf("%d%d%d%d%d%d", &n,&m,&q,&u,&v,&di); for(int i = 1; i <= n; i++) { scanf("%lld", buf+i); } sort(buf+1, buf+1+n); for(int i = n; i >= 1; i--) { que.push(buf[i]); } for(t = 1; t <= m; t++) { ll out; ll tmp, tmp1, tmp2; tmp = tmp1 = tmp2 = -200000000; if(!que.empty()) tmp = que.front(); if(!que1.empty()) tmp1 = que1.front(); if(!que2.empty()) tmp2 = que2.front(); if(tmp >= tmp1 && tmp >= tmp2)//tmp最大 { out = tmp; Process(que.front()); que.pop(); } else if(tmp1 >= tmp && tmp1 >= tmp2)//tmp1最大 { out = tmp1; Process(que1.front()); que1.pop(); } else //tmp2最大 { out = tmp2; Process(que2.front()); que2.pop(); } if(t % di == 0) { printf("%lld ", out + (t-1) * q); } } putchar('\n'); for(int i = 1; i <= n+m; i++) { ll out; ll tmp, tmp1, tmp2; tmp = tmp1 = tmp2 = -0x3f3f3f3f3f3f3f3f; if(que.size()) tmp = que.front(); if(que1.size()) tmp1 = que1.front(); if(que2.size()) tmp2 = que2.front(); if(tmp >= tmp1 && tmp >= tmp2)//tmp最大 { out = tmp; que.pop(); } else if(tmp1 >= tmp && tmp1 >= tmp2)//tmp1最大 { out = tmp1; que1.pop(); } else //tmp2最大 { out = tmp2; que2.pop(); } if(i % di == 0) printf("%lld ", out + q * (t-1)); } return 0; }
在这道题目中,一直都是段错误,细分析,是因为给负无穷大的时候给小了。
ACWing134. 双端队列
题面
达达现在碰到了一个棘手的问题,有 N个整数需要排序。
达达手头能用的工具就是若干个双端队列。她从 1到 N 需要依次处理这 N个数,对于每个数,达达能做以下两件事:
1.新建一个双端队列,并将当前数作为这个队列中的唯一的数;
2.将当前数放入已有的队列的头之前或者尾之后。
对所有的数处理完成之后,达达将这些队列按一定的顺序连接起来后就可以得到一个非降的序列。请你求出最少需要多少个双端序列。
输入格式
第一行输入整数 N,代表整数的个数。
接下来 N 行,每行包括一个整数 D**i,代表所需处理的整数。
输出格式
输出一个整数,代表最少需要的双端队列数。
数据范围
1≤N≤200000
输入样例:
6 3 6 0 9 6 3 输出样例:
2
题解
-
否定局部贪心:如果有p<q,且在一开始构成了一个队列,那么在之后,如果有一个数字位于p与q之间,那么就会没有解。
-
需要全局考虑。全局考虑先进行排序,然后就会有一些思路。
-
必须要把排序之后的进行分段,分成好几个小的区间,这样就可以满足 “达达将这些队列按一定的顺序连接起来后就可以得到一个非降的序列” 这一个条件。
-
现在还要考虑如何划分,才能满足“将当前数放入已有的队列的头之前或者尾之后或者新开一个队列”这样的条件。
-
由于排序,搞乱了现在与原来的关系,所以需要找到现在和原来输入顺序的关联(映射)。
-
-
上面的编号对应的是放进去的时间,在重新排序之后,成为红色部分。
-
对于每一段,都需要有一个最先放进去的(数字最小的),在它的两边,数字依次增加。
-
换句话说,就是找到一个具有单谷的段,并且极小值点的左边是单调递减,右边是单调递增。(该段内全部增加或者减少也可以,是特殊情况)
-
一定要注意:时间的排序(示意图最上面的哪一个是惟一的),but数值的大小是可以相同。所以有必要对于数值相同的进行一下交换,确保单谷的数目最多。
-
然后从左到右数出单谷区间的数目,这就是最终的答案。
不足总结
- 如果有两个变量,可以使用pair来代替结构体,因为pair更加简单并且sort函数可以直接对第一关键字进行排序。
错误代码
#include <bits/stdc++.h> using namespace std; #define N 20 //200005 typedef long long ll; struct Node { ll t; ll x; }s[N]; bool bijiao(Node a, Node b) { return a.x < b.x; } int main() { int n; cin >> n; for(int i = 1; i <= n; i++) {cin >> s[i].x; s[i].t = i;} sort(s+1, s+1+n, bijiao); bool tag = false;//tag为1,表示上升,为0表示下降。 ll last = LONG_LONG_MAX;//初始值为无穷大 ll ans = 0; for(int p = 1; p <= n; ) { int q = p; if((q+1) <= n && s[q+1].x == s[p].x) q++; ll maxx = s[p].t; ll minn = s[p].t; for(int j = p; j <= q; j++) { maxx = max(maxx, s[j].t); minn = min(minn, s[j].t); } if(tag)//表示上升 { if(minn < last) { tag = false; last = minn; } else { last = maxx; } } else//表示下降 { if(maxx > last) { ans++; tag = true; last = maxx; } else { last = minn; } } p = q+1; } printf("%lld\n", ans); return 0; }
错误之处:我的
- 计数方法
- maxx > last取不取等于号
与答案存在着矛盾。
如果输入
1 1 1
那么我的程序会输出0,显然错误!
我开始的时候数数的是凹下去的点的个数,但是因为最左边还有最右边的可能会缺少一半,所以不科学
eg:这个应该是有两个,但是我的程序会统计为一个。
数极大值的个数加上一就是答案,因为中间不可能缺少一半!
正确代码
#include <bits/stdc++.h> using namespace std; #define N 200005 typedef long long ll; struct Node { ll t; ll x; }s[N]; bool bijiao(Node a, Node b) { return a.x < b.x; } int main() { int n; cin >> n; for(int i = 1; i <= n; i++) {cin >> s[i].x; s[i].t = i;} sort(s+1, s+1+n, bijiao); bool tag = false;//tag为1,表示上升,为0表示下降。 ll last = LONG_LONG_MAX;//初始值为无穷大 ll ans = 1; for(int p = 1; p <= n; ) { int q = p; while((q+1) <= n && s[q+1].x == s[p].x) q++; ll maxx = s[p].t; ll minn = s[p].t; for(int j = p; j <= q; j++) { maxx = max(maxx, s[j].t); minn = min(minn, s[j].t); } if(tag)//表示上升 { if(minn < last) { tag = false; last = minn; ans++; } else { last = maxx; } } else//表示下降 { if(maxx > last) { tag = true; last = maxx; } else { last = minn; } } p = q+1; } printf("%lld\n", ans); return 0; }
ACWing135. 最大子序和
题面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k2ONQYIP-1656242960981)(队列.assets/image-20220626180600264.png)]
题解
对于一个区间的操作,可以使用前缀和来进行想象。
如果使用最暴力的方法来进行求解,便是枚举左右端点,显然不行。
**注意:**虽然枚举两遍是不可以的,但是枚举一遍还是可行的。
**注意:**对于前缀和的枚举,可以转化为滑动窗口。
理论推理:
考虑枚举右端点,此时,需要让右端点到左端点的S长度内找到一个最小的值。
对于单调队列,可以
- 快速找到端点(最优解)
- 借助单调性来进行排除。
单调队列的特点:
- 是单调的
- 一端可以插入,也可以删除
- 另一端可以删除(随着时间的推移,之前的元素会被删除!)
代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define N 300006 ll s[N]; deque<pair<ll,ll> > dq; int n, m; int main() { ll ans = LONG_LONG_MIN; cin >> n >> m; for(int i = 1; i <= n; i++) scanf("%lld", s+i); for(int i = 1; i <= n; i++) s[i] += s[i-1];//s数组里现在已经是前缀和 //dq.push_back(make_pair(0LL, 0)); for(ll i = 1; i <= n; i++) { while(dq.size() && dq.front().first < (i-m)) dq.pop_front(); while(dq.size() && dq.back().second >= s[i-1]) dq.pop_back(); dq.push_back(make_pair(i-1, s[i-1])); ans = max(ans,s[i] - dq.front().second); //if(i==6)调试专用代码!!! //{ // while(dq.size()) // { // printf("%d ", dq.front()); // dq.pop_front(); // // } // exit(0); //} } cout << ans; return 0; }
本文来自博客园,作者:心坚石穿,转载请注明原文链接:https://www.cnblogs.com/xjsc01/p/16438221.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律