51nod 3594 小明爱排队(算法预科PL1)

小明这天拿到一个排队题目,题意如下,一堆人排队,有几种操作,

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\) ,则出队操作是无效的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值