银行排队问题

本文解析了两个银行业务场景:一是两窗口异速服务模拟,二是单队列多窗口服务问题。涉及队列操作、优先级处理和平均等待时间计算,适合理解银行排队系统算法和性能优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文共包含两道题目,之所以将他们放在一起,不是因为两者具有类似的解法,仅仅是因为两道题都是涉及到银行。两道题的解法并没有延续关系或者关联关系。下面直接看题。

7-8 银行业务队列简单模拟 (25 分)

设某银行有A、B两个业务窗口,且处理业务的速度不一样,其中A窗口处理速度是B窗口的2倍 —— 即当A窗口每处理完2个顾客时,B窗口处理完1个顾客。给定到达银行的顾客序列,请按业务完成的顺序输出顾客序列。假定不考虑顾客先后到达的时间间隔,并且当不同窗口同时处理完2个顾客时,A窗口顾客优先输出。

输入格式:

输入为一行正整数,其中第1个数字N(≤1000)为顾客总数,后面跟着N位顾客的编号。编号为奇数的顾客需要到A窗口办理业务,为偶数的顾客则去B窗口。数字间以空格分隔。

输出格式:

按业务处理完成的顺序输出顾客的编号。数字间以空格分隔,但最后一个编号后不能有多余的空格。

输入样例:

8 2 1 3 9 4 11 13 15

结尾无空行

输出样例:

1 3 2 9 11 4 13 15

结尾无空行

 AC

#include <bits/stdc++.h>

#define FAST_IO                  \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0);

using namespace std;

int main()
{
    FAST_IO;
    queue<int> qa, qb;
    int n;
    cin >> n;
    while (n--)
    {
        int x;
        cin >> x;
        if (x % 2)
            qa.push(x);
        else
            qb.push(x);
    }
    int time = 1;
    while (!qa.empty() && !qb.empty())
    {
        cout << qa.front() << ' ';
        qa.pop();
        if (time % 2 == 0)
        {
            cout << qb.front() << ' ';
            qb.pop();
        }
        time++;
    }
    int a = qa.size(), b = qb.size();
    for (int i = 0; i < a; i++)
    {
        cout << qa.front();
        qa.pop();
        if (i != a - 1)
            cout << ' ';
    }
    for (int i = 0; i < b; i++)
    {
        cout << qb.front();
        qb.pop();
        if (i != b - 1)
            cout << ' ';
    }
    return 0;
}

回顾

这道题不算难,需要注意的一点是,在最后的输出阶段,需要提前设置好变量来记录每个队列的初始长度,假若让size函数出现在for循环当中,那么输出会出现错误,因为size函数是求当前队列的长度,每次我们在for循环内pop元素之后,队列的size都会改变。

这道题是在读入数据的同时,就把数据分类,即偶数去b队列,奇数去a队列。读取与处理同时进行节省了时间。

设置一个时间变量,每读一次a队列和b队列,时间都要加一,a队列是每个时间节点都可以输出,b队列是每隔一个时间节点才可以输出。 

7-9 银行排队问题之单队列多窗口服务 (25 分)

假设银行有K个窗口提供服务,窗口前设一条黄线,所有顾客按到达时间在黄线后排成一条长龙。当有窗口空闲时,下一位顾客即去该窗口处理事务。当有多个窗口可选择时,假设顾客总是选择编号最小的窗口。

本题要求输出前来等待服务的N位顾客的平均等待时间、最长等待时间、最后完成时间,并且统计每个窗口服务了多少名顾客。

输入格式:

输入第1行给出正整数N(≤1000),为顾客总人数;随后N行,每行给出一位顾客的到达时间T和事务处理时间P,并且假设输入数据已经按到达时间先后排好了顺序;最后一行给出正整数K(≤10),为开设的营业窗口数。这里假设每位顾客事务被处理的最长时间为60分钟。

输出格式:

在第一行中输出平均等待时间(输出到小数点后1位)、最长等待时间、最后完成时间,之间用1个空格分隔,行末不能有多余空格。

在第二行中按编号递增顺序输出每个窗口服务了多少名顾客,数字之间用1个空格分隔,行末不能有多余空格。

输入样例:

9
0 20
1 15
1 61
2 10
10 5
10 3
30 18
31 25
31 2
3

结尾无空行

输出样例:

6.2 17 61
5 3 1

结尾无空行

AC

#include <bits/stdc++.h>

using namespace std;


int main()
{
    int n, k, a[1001][2], b[11] = {0}, c[11] = {0};
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i][0] >> a[i][1];
        //“每位顾客事务每处理的最长时间为60分钟”
        a[i][1] = a[i][1] > 60 ? 60 : a[i][1];
    }
    cin >> k;
    int wait, max_wait = 0;
    double sum_wait = 0;
    for (int i = 0; i < n; i++)
    {
        //寻找“结束业务时间”最早的窗口
        int time = b[0], sign = 0;
        for (int j = 1; j < k; j++)
        {
            if (b[j] < time)
            {
                time = b[j];
                sign = j;
            }
        }
        //如果顾客到达时间早于最早的“结束业务时间”
        if (a[i][0] < time)
        {
            b[sign] += a[i][1];
            wait = time - a[i][0];
            sum_wait += wait;
            max_wait = max_wait > wait ? max_wait : wait;
            c[sign]++;
        }
        else
        {
            for (int j = 0; j < k; j++)
            {
                if (b[j] <= a[i][0])
                {
                    time = b[j];
                    sign = j;
                    break;
                }
            }
            b[sign] = a[i][0] + a[i][1];
            c[sign]++;
        }
    }
    //寻找“结束业务时间”最晚的时间点
    int max = b[0];
    for (int i = 1; i < k; i++)
        max = max > b[i] ? max : b[i];
    //输出
    printf("%.1lf %d %d\n", sum_wait / n, max_wait, max);
    for (int i = 0; i < k; i++)
    {
        if (i)
            cout << ' ';
        cout << c[i];
    }
    return 0;
}

回顾

        刚拿到这道题的时候,可以说是毫无头绪。于是就在网上查了许多uu们的代码,看了很多,思路可以说是大同小异,其中有一篇令我印象特别深刻,代码里用到了memset、优先队列...本着想读懂这篇代码的初心,我去查阅了堆以及许多其他的知识点,算是大差不差的顺了一遍代码。但是令我茅塞顿开或者说柳暗花明又一村的是一行注释或者说是一个变量——“该窗口结束营业的时间”。借着这个切入点以及结合了许多其他人的思路,我写出了自己的算法。

准备工作

随着写题数量的增加,慢慢地貌似找到了一点做题的方法。我认为,读完一道题目,如果不能立刻想出算法的话,不妨试着拿输入样例去模拟整个问题的计算过程,一步一步处理,进而得到相对应得输出样例。这样有助于理解解题过程。

观察输出格式

本题要求第一行输出平均等待时间——已知总人数(n),即我们需要记录等待时间总时长(sum_wait)。因为是平均数,所以要用到除法,故变量类型定义为double。

                                最大等待时间——我们需要算出每个人的等待时长(wait),并且记录最大值(max_wait)。变量类型定义为int。

                                结束时间——我们需要记录时间最长的那一个,即当我们处理完所有人之后,需要遍历窗口,找出“该窗口结束业务的时间”的最大值(数组b的最大值)。变量类型定义为int。

本题要求第二行输出每个窗口处理的人数——即需要设置一个数组(在AC中体现为数组c)来存储每个窗口所处理的人数,并且每处理一个人都需要及时更新。  变量类型定义为int。

 注

处理数据与读取数据的关系:有的题目是一边读取数据,一边处理数据。但是本题的结果显然与窗口数量(k)有关系,而窗口数量(k)又是在输入的最后,那么不难理解,本题是先把数据存取起来,最后再进行处理。

存储数据的方式:一共是三类数据。第一类是人数,用整型n存储即可;第二类是n行数据,每行有两个数,分别是每个人的到达时间以及事务所需处理时间,用整型二维数组存储即可,第一维是最大值1000,第二维用来存储数据,故规模是2足矣;第三类是窗口个数,用整型k存储即可。

算法

核心:循环嵌套(遍历每一个人(为该人选择合适的窗口) -> 遍历每一个窗口(找出当前所有窗口中结束业务时间最早的窗口))

自然语言描述:在存储数据之后,遍历数组a,即对每一个人进行处理,首先先找到当前所有窗口中结束业务时间最早的窗口,并且用sign记录下该窗口编号,用time记录下该窗口结束业务的时间,然后将time与a的第二维的第一个数,即顾客到达时间进行比较,若time更小,即窗口提前结束,即顾客不需要等待,则需要重新遍历每一个窗口,找出所有窗口中小于a的第二维的第一个数的且窗口编号最小的窗口,用sign做记录,让该人进入sign号窗口处理业务,更新sign号窗口结束业务时间,即a的第二维的第一个数与第二个数的和,并且sign所对应的数组c应该加一。假若time更大,那么说明顾客需要等待,那么记录当前顾客需要等待的时间,即time减a的第二维的第一个数,并且将time记录到总等待时间中去,然后将time与当前max_time作比较,取更大值,更新sign号窗口的结束业务时间,即加上a的第二维的第二个数,并且c记得加一。就这样依次处理每一个人的数据,直到全部处理完即可,然后进行相应的输出。

关于加速cin和cout

前几日学会了cin和cout的加速处理

#define FAST_IO                    \
    ios::sync_with_stdio(false);   \
    cin.tie(0);                    \
    cout.tie(0);

 自那以后,每次写c++的程序的时候,都会写上这么一段宏定义,为了速度更快嘛。但是今天在这道题里面出现了问题。这道题对平均等待时间的输出有一个要求,就是保留一位小数,c++的保留一位小数的书写格式相较于c来讲更复杂,更不好记忆(其实就是我没记住),所以我就采用了printf进行输出。在vscode中运行时,没有问题,但是在pta中测试时总显示答案错误,于是我就用了pta自己提供的编译器进行了debug,发现我的输出第一行和第二行换了顺序,即在vscode中第一行输出的内容跑到了第二行去,也就是说printf输出的东西跑到了cout输出的东西的后面,尽管我在代码中先写的printf后写的cout,可是出现了这种现象,原来这就是加速搞的鬼,因为我加速了,所以cout就先输出了,当我把加速关掉之后,发现答案正确,测试点全部过了。

通过这一点,我总结到,如果说在自己的编译器里面答案正确,但是在pta中错误的话,那么不妨试试用pta的编译器去debug,这样没准能找到错误的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗马尼亚硬拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值