【队列】----【Keep In Line】
题目描述
又到饭点了,SK 同学靠着惯性走到了食堂,但长长的队伍顿时让他失去了食欲。突然,他注意到某个窗口前的队伍里明显存在插队的现象,于是他默默记录下了同学们进队和出队的变化。
- 对于进队,SK 同学只知道队伍里多了一个人,并不知道新来的人是老老实实站到了队尾还是插到了队伍里的某个位置;
- 对于出队,SK 同学能确定是队伍里站在最前面的人出队了。
初始时队伍为空。
给出 n n n 条队伍进出的信息,保证已经出队的同学不会再入队,并且最终队伍也为空,SK 同学想知道有多少不插队的好同学。
输入描述
- 第一行是一个正整数 T ( 1 ≤ T ≤ 5 ) T \ (1 \leq T \leq 5) T (1≤T≤5),表示测试数据的组数。
- 对于每组测试数据:
- 第一行是一个整数 n ( 1 ≤ n ≤ 1 0 5 ) n \ (1 \leq n \leq 10^5) n (1≤n≤105),表示这个队伍进出的信息数。
- 接下来 n n n 行,每行为两个字符串
Opt
和Name
:Opt
为"in"
表示进队;Opt
为"out"
表示出队;Name
为进队或出队的人的名字,长度不超过 10;- 保证所有人的名字各不相同;
- 所有操作按时间顺序给出;
- 最终队列为空。
输出描述
对于每组测试数据,输出一行,包含一个整数,表示不插队的好同学人数。
示例1
输入
1
6
in quailty
in hwq1352249
out hwq1352249
in zhuaiballl
out quailty
out zhuaiballl
输出
2
题解:(模拟 + 贪心 + 哈希 + 有序集合)
一、问题抽象
队列是一个先进先出(FIFO)的结构:
- 入队:我们无法判断这个人是否站在了队尾;
- 出队:我们可以确定队首的人离队;
- 因此,只有在出队时才能判断是否插队。
我们对每个进入队伍的人打上一个时间戳(即第几个入队),就能通过时间顺序来判定插队行为:
- 如果一个人出队时,他不是当前入队最早且尚未出队的人 ⇒ 插队;
- 否则,他是好同学。
二、解题思路
1. 数据结构设计
- 用
unordered_map<string, int>
存储每个人的入队时间戳; - 用
unordered_map<string, bool>
存储每个人是否“未插队”; - 用
set<int>
存储当前仍在队中的人的时间戳集合(自动排序);
2. 时间戳分配
我们定义一个变量 one
,每当一个人入队时,递增分配一个时间戳,保证入队顺序唯一。
3. 插队判定逻辑
当一个人出队时:
- 查看当前队伍中最早的时间戳
min_timestamp
; - 如果该人的时间戳比
min_timestamp
大 ⇒ 他比最早入队的人还早出队 ⇒ 插队; - 否则,没插队。
无论是否插队,都要从当前队伍集合中删去该人的时间戳,表示其出队。
三、样例说明
输入:
1
6
in quailty
in hwq1352249
out hwq1352249
in zhuaiballl
out quailty
out zhuaiballl
处理过程:
- quailty 入队(时间戳 1);
- hwq1352249 入队(时间戳 2);
- hwq1352249 出队,但 quailty(时间戳 1)还没出 ⇒ 插队;
- zhuaiballl 入队(时间戳 3);
- quailty 出队(正常);
- zhuaiballl 出队(正常);
最终只有 quailty、zhuaiballl 是好同学,输出 2
。
四、完整代码:
#include <bits/stdc++.h>
using namespace std;// 定义 int 为 long long,避免 int 范围不够(尤其是时间戳可能很大)
#define int long longsigned main()
{// 读入测试组数int t;cin >> t;// 逐组处理while (t--){int n;cin >> n; // 读入本组中操作的总条数string a, b;// 记录每个人的入队时间戳(越早入队,时间戳越小)unordered_map<string, int> times;// 记录每个人是否是“好同学”(未插队),默认 true,若判定插队则置为 falseunordered_map<string, bool> bools;// 记录当前在队伍中的所有人(通过他们的时间戳),用 set 自动维护升序set<int> time;// 全局时间戳计数器,每次“in”操作时递增,给入队者分配时间戳int one = 0;// 处理 n 条操作while (n--){cin >> a >> b; // a 是操作类型(in/out),b 是人名if (a == "in"){// 入队操作one++; // 分配一个新的时间戳times[b] = one; // 记录该人的时间戳bools[b] = true; // 初始认为该人是“好同学”time.insert(one); // 将时间戳加入当前队列(表示他在队中)}else if (a == "out"){// 出队操作:只能从队首出队(即当前 time 中最小时间戳)auto it = time.begin(); // 队首对应的最小时间戳if (times[b] > (*it)){// 如果当前出队者的时间戳比队首还大// 说明他不是队伍里最早的人 => 插队了bools[b] = false;}// 无论是否插队,该人已出队,从集合中删除他的时间戳time.erase(times[b]);}}// 最终统计未插队的“好同学”人数int ans = 0;for (auto p : bools){if (p.second == true){ans++;}}// 输出本组的答案cout << ans << endl;}return 0;
}
五、时间复杂度分析
操作 | 复杂度 |
---|---|
每条 in / out 操作 | O ( l o g n ) O(log n) O(logn)(主要是 set 的插入/删除) |
总体(每组测试) | O ( n l o g n ) O(n log n) O(nlogn) |
总体(所有测试组) | O ( T ∗ n l o g n ) O(T * n log n) O(T∗nlogn),T ≤ 5,n ≤ 1e5 |
六、总结
- 本题核心思想是用时间戳记录顺序,再借助
set
维护在队人中谁是最早入队的; - 插队与否只在出队时判定,通过时间戳与当前队首的比较;
- 是一道典型的模拟 + 贪心判断 + 哈希 + 有序集合混合使用的好题,体现了实际调度逻辑模拟能力。