小组队列

0x12 队列 - 图1 个小组要排成一个队列,每个小组中有若干人。
当一个人来到队列时,如果队列中已经有了自己小组的成员,他就直接插队排在自己小组成员的后面,否则就站在队伍的最后面。
请你编写一个程序,模拟这种小组队列。
输入格式:
输入将包含一个或多个测试用例。
对于每个测试用例,第一行输入小组数量 0x12 队列 - 图2
接下来 0x12 队列 - 图3 行,每行输入一个小组描述,第一个数表示这个小组的人数,接下来的数表示这个小组的人的编号。
编号是 0x12 队列 - 图40x12 队列 - 图5 范围内的整数。
一个小组最多可包含 0x12 队列 - 图6 个人。
最后,命令列表如下。 有三种不同的命令:
1、ENQUEUE x - 将编号是 0x12 队列 - 图7 的人插入队列;
2、DEQUEUE - 让整个队列的第一个人出队;
3、STOP - 测试用例结束
每个命令占一行。
当输入用例 0x12 队列 - 图8 时,代表停止输入。
需注意:测试用例最多可包含 0x12 队列 - 图90x12 队列 - 图10 万)个命令,因此小组队列的实现应该是高效的:
入队和出队都需要使用常数时间。
输出样例
对于每个测试用例,首先输出一行 Scenario #k,其中 0x12 队列 - 图11 是测试用例的编号。
然后,对于每个 DEQUEUE 命令,输出出队的人的编号,每个编号占一行。
在每个测试用例(包括最后一个测试用例)输出完成后,输出一个空行。
数据范围
0x12 队列 - 图12
输入样例:

  1. 2
  2. 3 101 102 103
  3. 3 201 202 203
  4. ENQUEUE 101
  5. ENQUEUE 201
  6. ENQUEUE 102
  7. ENQUEUE 202
  8. ENQUEUE 103
  9. ENQUEUE 203
  10. DEQUEUE
  11. DEQUEUE
  12. DEQUEUE
  13. DEQUEUE
  14. DEQUEUE
  15. DEQUEUE
  16. STOP
  17. 2
  18. 5 259001 259002 259003 259004 259005
  19. 6 260001 260002 260003 260004 260005 260006
  20. ENQUEUE 259001
  21. ENQUEUE 260001
  22. ENQUEUE 259002
  23. ENQUEUE 259003
  24. ENQUEUE 259004
  25. ENQUEUE 259005
  26. DEQUEUE
  27. DEQUEUE
  28. ENQUEUE 260002
  29. ENQUEUE 260003
  30. DEQUEUE
  31. DEQUEUE
  32. DEQUEUE
  33. DEQUEUE
  34. STOP
  35. 0

输出样例:

  1. Scenario #1
  2. 101
  3. 102
  4. 103
  5. 201
  6. 202
  7. 203
  8. Scenario #2
  9. 259001
  10. 259002
  11. 259003
  12. 259004
  13. 259005
  14. 260001

队列模拟(大队列套小队列)
可以使用 STL 来优化编码

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. void solve(int n) {
  4. unordered_map<int, int> id, in;
  5. vector<queue<int>> q(n + 1);
  6. queue<int> Q;
  7. for(int i = 1; i <= n; i++){
  8. int cnt, y;
  9. scanf("%d", &cnt);
  10. for(int j = 1; j <= cnt; j++){
  11. scanf("%d", &y);
  12. id[y] = i;
  13. }
  14. }
  15. string o;
  16. while(cin >> o) {
  17. if(o[0] == 'E') {
  18. int x; scanf("%d", &x);
  19. if(in.count(id[x])) {
  20. q[id[x]].push(x);
  21. } else {
  22. Q.push(id[x]);
  23. q[id[x]].push(x);
  24. in[id[x]] = 1;
  25. }
  26. } else if(o[0] == 'D') {
  27. int qid = Q.front();
  28. printf("%d\n", q[qid].front());
  29. q[qid].pop();
  30. if(q[qid].size() == 0) {
  31. Q.pop();
  32. in.erase(qid);
  33. }
  34. } else {
  35. break;
  36. }
  37. }
  38. puts("");
  39. }
  40. int main(){
  41. int n, cas = 0;
  42. while(~scanf("%d", &n) && n) {
  43. printf("Scenario #%d\n", ++cas);
  44. solve(n);
  45. }
  46. }

蚯蚓

蛐蛐国最近蚯蚓成灾了!
隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓。
蛐蛐国里现在共有 0x12 队列 - 图13 只蚯蚓,第 0x12 队列 - 图14 只蚯蚓的长度为 0x12 队列 - 图15 ,所有蚯蚓的长度都是非负整数,即可能存在长度为 0x12 队列 - 图16 的蚯蚓。
每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只,将其切成两段。
若有多只最长的,则任选一只。
神刀手切开蚯蚓的位置由有理数 0x12 队列 - 图17 决定。
一只长度为 0x12 队列 - 图18 的蚯蚓会被切成两只长度分别为 0x12 队列 - 图190x12 队列 - 图20 的蚯蚓。
特殊地,如果这两个数的其中一个等于 0x12 队列 - 图21,则这个长度为 0x12 队列 - 图22 的蚯蚓也会被保留。
此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加一个非负整数 0x12 队列 - 图23
蛐蛐国王知道这样不是长久之计,因为蚯蚓不仅会越来越多,还会越来越长。
蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要 0x12 队列 - 图24 秒才能到来。
蛐蛐国王希望知道这 0x12 队列 - 图25 秒内的战况。
具体来说,他希望知道:

  1. 0x12 队列 - 图26 秒内,每一秒被切断的蚯蚓被切断前的长度,共有 0x12 队列 - 图27 个数。
  2. 0x12 队列 - 图28 秒后,所有蚯蚓的长度,共有 0x12 队列 - 图29 个数。

输入格式
第一行包含六个整数 0x12 队列 - 图30,其中:0x12 队列 - 图31 的意义参考题目描述;0x12 队列 - 图32 均为正整数;你需要自己计算 0x12 队列 - 图33(保证 0x12 队列 - 图34);0x12 队列 - 图35 是输出参数,其含义将会在输出格式中解释。
第二行包含 0x12 队列 - 图36 个非负整数,为 0x12 队列 - 图37,即初始时 0x12 队列 - 图38 只蚯蚓的长度。
同一行中相邻的两个数之间,恰好用一个空格隔开。

输出格式
第一行输出 0x12 队列 - 图39 个整数,按时间顺序,依次输出第 0x12 队列 - 图40 秒,第 0x12 队列 - 图41 秒,第 0x12 队列 - 图42 秒,……被切断蚯蚓(在被切断前)的长度。
第二行输出 0x12 队列 - 图43%2Ft%E2%8C%8B#card=math&code=%E2%8C%8A%28n%2Bm%29%2Ft%E2%8C%8B&id=VTr1q) 个整数,输出 0x12 队列 - 图44 秒后蚯蚓的长度;需要按从大到小的顺序,依次输出排名第 0x12 队列 - 图45,第 0x12 队列 - 图46,第 0x12 队列 - 图47,……的长度。
同一行中相邻的两个数之间,恰好用一个空格隔开。
即使某一行没有任何数需要输出,你也应输出一个空行。
请阅读样例来更好地理解这个格式。
数据范围
0x12 队列 - 图48,
0x12 队列 - 图49,
0x12 队列 - 图50,
0x12 队列 - 图51,
0x12 队列 - 图52,
0x12 队列 - 图53,
0x12 队列 - 图54
输入样例:

  1. 3 7 1 1 3 1
  2. 3 3 2

输出样例:

  1. 3 4 4 4 5 5 6
  2. 6 6 6 5 5 4 4 3 2 2

样例解释
样例中,在神刀手到来前:0x12 队列 - 图55 只蚯蚓的长度为 0x12 队列 - 图56
0x12 队列 - 图57 秒后:一只长度为 0x12 队列 - 图58 的蚯蚓被切成了两只长度分别为 0x12 队列 - 图590x12 队列 - 图60 的蚯蚓,其余蚯蚓的长度增加了 0x12 队列 - 图61。最终 0x12 队列 - 图62 只蚯蚓的长度分别为 0x12 队列 - 图63%2C4%2C3#card=math&code=%281%2C2%29%2C4%2C3&id=YMxPi)。 括号表示这个位置刚刚有一只蚯蚓被切断。
0x12 队列 - 图64 秒后:一只长度为 0x12 队列 - 图65 的蚯蚓被切成了 0x12 队列 - 图660x12 队列 - 图670x12 队列 - 图68 只蚯蚓的长度分别为:0x12 队列 - 图69%2C4#card=math&code=2%2C3%2C%281%2C3%29%2C4&id=v32h5)。
0x12 队列 - 图70 秒后:一只长度为 0x12 队列 - 图71 的蚯蚓被切断。0x12 队列 - 图72 只蚯蚓的长度分别为:0x12 队列 - 图73#card=math&code=3%2C4%2C2%2C4%2C%281%2C3%29&id=bzlrP)。
0x12 队列 - 图74 秒后:一只长度为 0x12 队列 - 图75 的蚯蚓被切断。0x12 队列 - 图76 只蚯蚓的长度分别为:0x12 队列 - 图77%2C3%2C5%2C2%2C4#card=math&code=4%2C%281%2C3%29%2C3%2C5%2C2%2C4&id=MjOps)。
0x12 队列 - 图78 秒后:一只长度为 0x12 队列 - 图79 的蚯蚓被切断。0x12 队列 - 图80 只蚯蚓的长度分别为:0x12 队列 - 图81%2C3%2C5#card=math&code=5%2C2%2C4%2C4%2C%281%2C4%29%2C3%2C5&id=cZP6M)。
0x12 队列 - 图82 秒后:一只长度为 0x12 队列 - 图83 的蚯蚓被切断。0x12 队列 - 图84 只蚯蚓的长度分别为:0x12 队列 - 图85%2C3%2C5%2C5%2C2%2C5%2C4%2C6#card=math&code=%281%2C4%29%2C3%2C5%2C5%2C2%2C5%2C4%2C6&id=Tfzqt)。
0x12 队列 - 图86 秒后:一只长度为 0x12 队列 - 图87 的蚯蚓被切断。0x12 队列 - 图88 只蚯蚓的长度分别为:0x12 队列 - 图89#card=math&code=2%2C5%2C4%2C6%2C6%2C3%2C6%2C5%2C%282%2C4%29&id=djYwN)。
所以,0x12 队列 - 图90 秒内被切断的蚯蚓的长度依次为 0x12 队列 - 图91
0x12 队列 - 图92 秒后,所有蚯蚓长度从大到小排序为 0x12 队列 - 图93


该题是 NOIP2016 提高组的 D2T2
直接用优先队列做可以得85分。
注意到最一开始的元素从小到大按比例分割之后,大小顺序依旧保留,只是被分成了左右两个部分。
比如 0x12 队列 - 图940x12 队列 - 图95被分成了0x12 队列 - 图960x12 队列 - 图97被分成了 0x12 队列 - 图98,那么仍然有0x12 队列 - 图99
对于分割之后的,用两个队列维护即可。
总体复杂度 0x12 队列 - 图100

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=100010, inf = 1e9;
  4. int n, m, u, v, q, t;
  5. int a[N];
  6. queue<int> q1, q2, q3;
  7. int get(){
  8. int x, a, b, c;
  9. a = b = c = -inf;
  10. if(q1.size()) a = q1.front();
  11. if(q2.size()) b = q2.front();
  12. if(q3.size()) c = q3.front();
  13. x = max({a, b, c});
  14. if(x == a) q1.pop();
  15. else if(x == b) q2.pop();
  16. else if(x == c) q3.pop();
  17. return x;
  18. }
  19. int main(){
  20. scanf("%d%d%d%d%d%d", &n, &m, &q, &u, &v, &t);
  21. for(int i=1;i<=n;i++){
  22. scanf("%d", &a[i]);
  23. }
  24. sort(a + 1, a + 1 + n, greater<int>());
  25. for(int i=1;i<=n;i++) q1.push(a[i]);
  26. for(int i=1;i<=m;i++){
  27. int x = get();
  28. x += (i - 1) * q;
  29. if(i % t == 0) printf("%d ", x);
  30. int l = 1ll * x * u / v;
  31. int r = x - l;
  32. // printf("%d %d\n", l, r);
  33. q2.push(l - i * q);
  34. q3.push(r - i * q);
  35. }
  36. puts("");
  37. for(int i=1;i<=n+m;i++){
  38. int x = get() + m * q;
  39. if(i % t == 0) printf("%d ", x);
  40. }
  41. puts("");
  42. }

双端队列

达达现在碰到了一个棘手的问题,有 0x12 队列 - 图101 个整数需要排序。
达达手头能用的工具就是若干个双端队列。
她从 0x12 队列 - 图1020x12 队列 - 图103 需要依次处理这 0x12 队列 - 图104 个数,对于每个数,达达能做以下两件事:
1.新建一个双端队列,并将当前数作为这个队列中的唯一的数;
2.将当前数放入已有的队列的头之前或者尾之后。
对所有的数处理完成之后,达达将这些队列按一定的顺序连接起来后就可以得到一个非降的序列。
请你求出最少需要多少个双端序列。
输入格式
第一行输入整数 0x12 队列 - 图105,代表整数的个数。
接下来 0x12 队列 - 图106 行,每行包括一个整数 0x12 队列 - 图107,代表所需处理的整数。
输出格式
输出一个整数,代表最少需要的双端队列数。
数据范围
0x12 队列 - 图108
输入样例:

  1. 6
  2. 3
  3. 6
  4. 0
  5. 9
  6. 6
  7. 3

输出样例:

  1. 2

此题难度较大。
例如序列 [3, 6, 0, 9, 6, 3],给每个数字组合成一个 pair,分别表示值和所在下标。
排序后可得:[(0,3), (3,1), (3, 6), (6, 2), (6, 5), (9, 4)]
容易想到:对于值相同的一段,在双端队列中应当是连续的,那么上述数组可以按值划分成若干段:[0, 3, 6, 9],其中 0 的出现坐标是 3, 3出现的坐标是 1 和 6。
在顺序遍历过程中,往双端队列中添加元素,最终队列中的元素下标一定是先递减再递增的。所以我们希望这样的下标规律也尽可能的在最终排好序的序列中。
由于 3 在 1 和 6 中都出现了,所以最终在队列中 1 和 6 必须挨着。我们可以按照 1、6 的顺序将其添加到队列尾部,或者按照 1、6 的顺序将其添加到队列的首部,这样都是符合要求的。只要最终队列中的坐标是先递减再递增即可。
由此,我们可以翻转等值元素的 pos 区间。记录等值元素出现的最小位置以及最大位置,然后从小到大遍历每个元素,尝试将当前区间翻转或者不翻转,使得坐标变化的低谷个数最少。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. int main(){
  7. int n;
  8. scanf("%d", &n);
  9. vector<pair<int,int>> a(n + 1);
  10. for(int i = 1; i <= n ;i++) {
  11. scanf("%d", &a[i].first);
  12. a[i].second = i;
  13. }
  14. sort(a.begin() + 1, a.end());
  15. vector<int> l, r;
  16. l.emplace_back(a[1].second);
  17. for(int i = 1; i < n; i++){
  18. if(a[i].first != a[i + 1].first) {
  19. r.emplace_back(a[i].second);
  20. l.emplace_back(a[i + 1].second);
  21. }
  22. }
  23. r.emplace_back(a[n].second);
  24. // 第 i 段等值区间为 [l[i], r[i]]
  25. // f = false 表示现在遍历过程中处于递减状态,由于我们希望低谷最少,所以起初设置为递减状态是较优的。
  26. bool f = false;
  27. int h = 1E9, rs = 0;
  28. for(int i = 0; i < l.size(); i++){
  29. if(f) {
  30. // 处于递增状态
  31. if(l[i] > h) {
  32. // 递增状态无需变化
  33. h = r[i];
  34. } else {
  35. // 递增状态打破,[l[i], r[i]] 区间翻转
  36. h = l[i];
  37. f = false;
  38. }
  39. } else {
  40. // 处于递减状态
  41. if(r[i] < h) {
  42. // 递减状态无需变化
  43. h = l[i];
  44. } else {
  45. h = r[i];
  46. rs ++;
  47. f = true;
  48. }
  49. }
  50. }
  51. // 最后的那部分处于递减的区间,也需要一个新的双端队列来处理
  52. if(f == false) rs ++;
  53. printf("%d\n", rs);
  54. return 0;
  55. }

最大子序和

输入一个长度为 0x12 队列 - 图109 的整数序列,从中找出一段长度不超过 0x12 队列 - 图110 的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是 0x12 队列 - 图111
输入格式
第一行输入两个整数 0x12 队列 - 图112
第二行输入 0x12 队列 - 图113 个数,代表长度为 0x12 队列 - 图114 的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
0x12 队列 - 图115
输入样例:

  1. 6 4
  2. 1 -3 5 1 -2 3

输出样例:

  1. 7

s[i] 表示前 i 项的和,则连续子序列 [L,R] 中数的和就等于 s[R]-s[L-1]
原问题转换为:找出两个位置 x,y,使得 s[y]-s[x] 最大并且y-x < m
枚举右端点 i,当 i 固定时,问题变为:找到一个左端点 j,其中 0x12 队列 - 图116并且 s[j] 最小。
不妨比较任意两个位置 jk,如果 k < j < i 并且 s[k] > s[j],那么对于所有大于等于 i 的右端点,k 永远不会成为最优选择。
所以,可能成为最优选择的策略集合一定是一个下标位置递增,对应的前缀和 S 的值也递增的序列
我们可以用一个队列来保存这个序列,随着右端点从前向右扫描,对于每个 i 执行以下操作:

  1. 判断队头决策与 i 的距离是否超过 M 的范围,若超出则出队。
  2. 此时队头就是右端点为 i 时,左端点 j 的最优选择。
  3. 不断删除队尾决策,直到队尾对应的 S 值小于 s[i]。然后把 i 作为一个新的决策入队。
    1. #include <bits/stdc++.h>
    2. using namespace std;
    3. const int N = 300010;
    4. typedef long long ll;
    5. ll a[N],sum[N];
    6. int q[N],l,r;
    7. int n,m;
    8. int main(){
    9. scanf("%d%d",&n,&m);
    10. for(int i=1;i<=n;i++){
    11. scanf("%lld",&a[i]);
    12. sum[i] = sum[i-1] + a[i];
    13. }
    14. l = 0,r = -1;
    15. q[++r] = 0;
    16. ll res = 0;
    17. for(int i=1;i<=n;i++){
    18. while(l <= r && i - q[l] > m)l++;
    19. res = max(res,sum[i] - sum[q[l]]);
    20. while(l <= r && sum[i] <= sum[q[r]])r--;
    21. q[++r] = i;
    22. }
    23. cout << res << endl;
    24. return 0;
    25. }