小明这天拿到一个排队题目,题意如下,一堆人排队,有几种操作,
a v
1.如果v不在队中,则表示v来了,排在队尾;
2.如果v在队中,则表示v走了。
b
接待队首的一客,输出顾客是谁。
如果不存在输出“-1”。
对于小明来说,这个问题他不会做,聪明的你可以帮助小明解决这个问题吗?
输入格式
第一行输入一个n(1<=n<=100000),表示操作数; 之后n行,如果第一个字符是a,那么后面继续输入一个数字v,具体意义如下: 1.如果v不在队中,则表示v来了,排在队尾; 2.如果v在队中,则表示v走了。 如果第一个字符是b,那么表示询问队首的顾客,输出顾客是谁。
输出格式
对于每个询问b,如果存在答案则输出顾客的编号,否则输出“-1”。
输入样例
7
a 1
a 1
a 2
a 1
b
b
b
输出样例
2
1
-1
数据范围
1<=n<=100000 0<=v<=10000
样例解释
操作a 1,编号为1的人进入队列,当前队列为1;
操作a 1,编号为1的人退出队列,当前队列为空;
操作a 2,编号为2的人进入队列,当前队列为2;
操作a 1,编号为1的人进入队列,当前队列为2 1;
操作b,队首为2,编号为2的人退出,当前队列为1;
操作b,队首为1,编号为1的人退出,当前队列为空;
操作b,当前队列为空,输出“-1”。
51nod标程:
①
我们用一个队列来模拟这个排队的过程,用一个 boolbool 数组 IdId ,记录编号为 ii 的人是否在队列中。
遇到 aa 操作,我们查看编号为 vv 的人是否在队列中,如果是新来一个人,则把他加入到队列的末尾,如果已经在队列中了,则从队列中删掉该用户。
遇到 bb 操作,我们直接弹出队列的队首元素即可。
上面的模拟看起来很完美,但也存在一个重大的问题,对于一个队列来说,他不支持删除队列中间位置元素,这就需要我们使用一些技巧了。我们并不立即删除这个元素,而是在最终弹出队首元素时再处理。也就是说遇到 bb 操作时,如果发现弹出的元素是无效的,则不做输出,继续弹出下一个。
那么如何判断弹出的元素是否有效呢?一个简单的想法,我们不是有一个 IdId 数组来记录每个人是否在队列中么?在队列中就认为是有效的,不在则认为是无效的。这个方法其实是错的,因为存在一个人走了又来了的情况。从记录来看,这个人仍然在队列中,但位置已经发生了变化,前面的元素是无效的。
为了处理这种情况,我们给每一个队列中的元素带一个附带值(可以利用结构体记录),这个附带值是当前操作的序号( 1−>n1−>n ),同时还要记录每个人最后一次入队操作的序号。当我们弹出元素时,发现对应的序号并不是最后一次入队操作的序号时,则认为当前元素是无效的。
我们把 boolbool 数组 IdId 改为 intint 数组,用来记录最后一次操作的序号,如果序号为 00 ,则表明这个人不在队列中。因此在编号为 vv 的用户出队的时候,我们需要把对应的 Id[v]=0Id[v]=0 。经过这样的修改,依靠队列就可以处理所有要求的操作了。
#include <bits/stdc++.h>
using namespace std;
struct node { int x, y; };
int Id[10010];
queue<node> Q;
int main() {
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
char ch;
cin >> ch;
if (ch == 'a') {
int x;
cin >> x;
if (Id[x] == 0) {
Q.push(node{ x, i });
Id[x] = i;
}
else
Id[x] = 0;
}
else {
int f = 0;
while (!Q.empty()) {
node F = Q.front();
Q.pop();
if (Id[F.x] == F.y) {
f = 1;
cout << F.x << endl;
Id[F.x] = 0;
break;
}
}
if (f == 0)
cout << -1 << endl;
}
}
return 0;
}
②
除了上面的方法,还有另外一个思路可以解决这个问题,与之前思路相同的地方在于,队列不支持删除中间位置元素,所以这个方法同样是不立即删除离队元素,而是在最终弹出队首元素时再处理。处理 \(b\) 操作时,每次从队首取到元素,我们要判断是否有效,有效才输出这个元素。
仍然使用 \(bool\) 数组 \(isIn\) 记录编号为 \(i\) 的人是否在队列中,再定义一个 \(int\) 数组 \(cnt\) 记录编号为 \(i\) 的人进入队列的次数。 \(i\) 每经历一次出队再入队,队列中就会多一个 \(i\) ,这其中只有最后一次入队的 \(i\) 是有效的。因此每次入队,我们将 \(cnt[i]\) 加一,从队首弹出时将 \(cnt[i]\) 减一,这样只有当 \(cnt[i]==0\) 的时候,才表明刚才那次操作中 \(i\) 是真的离开队列了,此时队列中的 \(i\) 是有效的。如
#include <bits/stdc++.h>
using namespace std;
int const maxN = 10010;
bool isIn[maxN];
int cnt[maxN];
queue<int> Q;
int main() {
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
char ch;
cin >> ch;
if (ch == 'a') {
int x;
cin >> x;
if (!isIn[x]) {
Q.push(x);
cnt[x]++;
}
isIn[x] = !isIn[x];
}
else {
int f = 0;
while (!Q.empty()) {
int v = Q.front();
Q.pop();
cnt[v]--;
if (cnt[v] == 0 && isIn[v]) {
f = 1;
isIn[v] = false;
cout << v << endl;
break;
}
}
if (f == 0)
cout << -1 << endl;
}
}
return 0;
}
果 \(isIn[i]\) 为 \(0\) 或 \(cnt[i] > 0\) ,则出队操作是无效的。