pat甲级1026. Table Tennis (30)

欢迎访问我的pat甲级题解目录哦https://blog.csdn.net/richenyunqi/article/details/84981078

题目描述

pat甲级1026. Table Tennis (30)_第1张图片

算法设计

本题应该是甲级题集模拟题中最复杂、难度最高、坑最多的一道题了。说一下我的算法,如果有读者看过我在pat甲级1017. Queueing at Bank (25)、pat甲级1014.Waiting in Line(30)的博客的话,应该会了解一些我做这些模拟题目比较喜欢使用优先级队列,这道题也不例外,我也是用优先级队列来实现的,不过这道题实现起来要比1017、1014困难得多。

首先我定义了两个类Player和Table,分别存储关于乒乓球运动员和乒乓球台的一些信息,定义代码如下:

struct Player{
    int arriveTime=0,processTime=0;//到达时间、打乒乓球的时间
    bool vip=false;//是否是VIP运动员
};
struct Table{
    bool occupy=false,vip=false;//目前是否被占用、是否是VIP球台
    int serverPlayerNum=0;//一天内服务的运动员数量
};

然后定义了Player和Table的两个数组,存储不同运动员和球台的相关信息,并且这两个数组信息被读入以后数组下标和元素的对应关系不再改变

接着我定义了第三个类,也是要放入优先级队列的类PlayerTable,先上代码:

struct PlayerTable{
    int tableNum,playerNum,time;//球台编号、运动员编号、时间
    PlayerTable(int table,int p,int t):tableNum(table),playerNum(p),time(t){}//构造函数
    bool operator <(const PlayerTable&p)const{//重载<运算符
        return this->time>p.time;
    }
};

由于Player和Table一旦被读入以后,下标和元素对应关系不再改变,就可以用相应的数组下标唯一标识一个运动员或者球台。PlayerTable这个类主要是为了确定当前是哪个运动员在使用哪个球台,并且时间既可以代表运动员的到达时间也可以指代运动员的离开时间,通过重载小于运算符使得优先级队列的队首元素总是当前队列中时间最早的元素。此外,由于在模拟过程中对于VIP乒乓球台,如果等待序列中存在VIP运动员,需要从等待序列中找出VIP运动员将这一球台分配给他,并将其从等待序列中移除,这涉及到从容器中间的删除操作,实现这种操作最合适的容器莫过于stl中的list,所以我又定义了一个list容器存储等待序列。综上,我的类定义及数据结构的选取代码如下:

struct Player{
    int arriveTime=0,processTime=0;//到达时间、打乒乓球的时间
    bool vip=false;//是否是VIP运动员
};
struct Table{
    bool occupy=false,vip=false;//目前是否被占用、是否是VIP球台
    int serverPlayerNum=0;//一天内服务的运动员数量
};
Player player[10005];//运动员数组
Table table[105];//乒乓球台数组
struct PlayerTable{
    int tableNum,playerNum,time;//球台编号、运动员编号、时间
    PlayerTable(int table,int p,int t):tableNum(table),playerNum(p),time(t){}//构造函数
    bool operator <(const PlayerTable&p)const{//重载<运算符
        return this->time>p.time;
    }
};
priority_queueplayerTable;//优先级队列
listwaitPlayer;//等待序列

接下来大致叙述一下我的算法逻辑:

由于球台从1~N编号,我在读入Player信息时直接将Player编号和对应的到达时间以及0号球台构成的PlayerTable对象压入优先级队列,利用球台编号是不是0可以区分当前编号的Player是否需要分配球台。

接着,不断弹出优先级队列队首元素,判断弹出的队首元素的球台编号是否为0,并执行以下操作:

1. 为0,元素的时间代表运动员的到达时间,需要分配球台,遍历Table数组,查看有无空闲的球台,

(1)如果有且不唯一,选取编号最小的(如果有空闲的VIP球台,VIP运动员选取VIP球台中编号最小的),分配给该元素运动员编号下的运动员,输出该运动员接受服务的时间及其他要求输出的信息,然后将该运动员编号、选取的球台编号以及离开时间压入优先级队列

(2)没有空闲球台,将该运动员编号加入等待队列

2. 不为0,元素的时间代表运动员的离开时间,不需要分配球台,且该元素的球台编号下的球台可以做空闲球台使用,查看等待队列是否为空

(1)为空,直接将该球台闲置

(2)不为空,分两种情况:

        如果该球台不是VIP球台,直接将该球台分配给等待序列中第一个人,输出该运动员接受服务的时间及其他要求输出的信息,然后将该运动员编号、选取的球台编号以及离开时间压入优先级队列

       如果该球台是VIP球台,遍历等待序列,查找第一个VIP运动员,找到了,将该球台分配给他;没找到分配给等待序列中第一个人。输出分配球台的运动员接受服务的时间及其他要求输出的信息,然后将分配球台的运动员编号、选取的球台编号以及离开时间压入优先级队列

重复以上弹出队首元素及相关操作的步骤直至优先级队列为空,就可以按要求输出所有运动员接受服务的时间了。

采用这种方法的好处是,通过优先级队列直接把运动员到达时间和球台的空闲时间(即运动员离开时间)按时间顺序糅合在一起,不用自己编码进行繁琐的时间比较。

注意点

1. 如果有空闲的VIP球台,vip运动员总数优先选择编号最小的VIP球台,如果没有空闲的VIP球台才会考虑普通球台。

2. 普通运动员也可以占VIP球台,当有多个桌子空闲时选择编号最小的桌。或者说在普通运动员眼里,是没有VIP与非VIP球台区别的

3. 运动员占用球台的时间不能超过2小时,超过2小时也要按2小时计算。

4. 等待时间四舍五入,即30s要进位成1min,可以利用库函数中的round函数四舍五入取整。

5. 如果21点前还没有找到球台,是不能在接受服务的了。

另外,我看到在一些论述这道题的博客中,有写道要注意处理早于8点和晚于21点到达的运动员数据,题目中已有声明说所有运动员到达时间都在8点和21点之间,为此我亲身实践了一下:

scanf("%d",&N);
    for(int i=0;i21*3600)
            printf("0000");
        player[i].processTime=min(player[i].processTime,120);
        player[i].processTime*=60;
        playerTable.push(PlayerTable(0,i,player[i].arriveTime));
    }

这是读取数据的代码,我在得到运动员到达的时间之后,在7-8行加了一条判断语句,如果到达时间早于8点或晚于21点,输出0000,由于pat评测系统依靠输出判断代码运行正确与否,而且我的代码是AC的,所以如果这道题的测试点中真的有包含早于8点或晚于21点的数据的话,相关测试点会判错,但是我提交之后结果是这样:

pat甲级1026. Table Tennis (30)_第2张图片

依然是AC的,可见题目中的信息是准确的,不存在早于8点或晚于21点到达的运动员数据,所以读者就不必费心编写代码处理这些边界数据了。

我又把代码修改了一下:

scanf("%d",&N);
    for(int i=0;i

提交之后是这样的:

pat甲级1026. Table Tennis (30)_第3张图片

可见测试点3是包含正好等于21点的测试数据的。

再次修改代码:

scanf("%d",&N);
    for(int i=0;i

提交之后:

pat甲级1026. Table Tennis (30)_第4张图片

看来包含8点到达的运动员数据的测试点还是很多的。

C++代码

#include
using namespace std;
struct Player{
    int arriveTime=0,processTime=0;//到达时间、打乒乓球的时间
    bool vip=false;//是否是VIP运动员
};
struct Table{
    bool occupy=false,vip=false;//目前是否被占用、是否是VIP球台
    int serverPlayerNum=0;//一天内服务的运动员数量
};
Player player[10005];//运动员数组
Table table[105];//乒乓球台数组
struct PlayerTable{
    int tableNum,playerNum,time;//球台编号、运动员编号、时间
    PlayerTable(int table,int p,int t):tableNum(table),playerNum(p),time(t){}//构造函数
    bool operator <(const PlayerTable&p)const{//重载<运算符
        return this->time>p.time;
    }
};
priority_queueplayerTable;//优先级队列
listwaitPlayer;//等待序列
const int closeTime=21*3600;//体育馆关闭时间
int N,M,K;
int searchTable(bool vip){//查找空闲的球台,如果运动员是vip,优先查找vip球台。查找到返回该球台的索引,否则返回-1
    if(vip)
        for(int i=1;i<=K;++i)
            if(!table[i].occupy&&table[i].vip)
                return i;
    for(int i=1;i<=K;++i)
        if(!table[i].occupy)
            return i;
    return -1;
}
void output(int t1,int t2){//输出到达时间t1,接受服务时间t2,两者之间的时间间隔(注意这里以分为单位,且需四舍五入)
    printf("%02d:%02d:%02d %02d:%02d:%02d %d\n",t1/3600,t1/60%60,t1%60,t2/3600,t2/60%60,t2%60,(t2-t1+30)/60);
}
int main(){
    scanf("%d",&N);
    for(int i=0;i=closeTime)//如果队首元素时间晚于关闭时间,跳出循环
            break;
        if(p.tableNum==0){//球台编号为0,表示需要分配球台
            int index=searchTable(player[p.playerNum].vip);//查找空闲球台
            if(index!=-1){//查找到更新相关信息
                table[index].occupy=true;//该球台被占用
                int endTime=p.time+player[p.playerNum].processTime;//获取该球台的空闲时间压入优先级队列
                playerTable.push(PlayerTable(index,p.playerNum,endTime));
                output(player[p.playerNum].arriveTime,p.time);//输出
                ++table[index].serverPlayerNum;//递增该球台服务人数
            }else//查找不到空闲球台归入等待序列
                waitPlayer.push_back(p.playerNum);
        }else{//球台编号为0,表示该球台可以闲置
            if(waitPlayer.empty())//如果等待序列为空
                table[p.tableNum].occupy=false;//球台闲置
            else{//如果等待序列不空
                int temp;
                if(!table[p.tableNum].vip){//球台非VIP分配给等待序列队首元素
                    temp=waitPlayer.front();
                    waitPlayer.pop_front();
                }else{//球台是VIP
                    auto i=waitPlayer.begin();
                    while(i!=waitPlayer.end()&&!player[*i].vip)//查找等待序列中有无VIP运动员
                        ++i;
                    if(i==waitPlayer.end()){//没有分配给等待序列队首元素
                        temp=waitPlayer.front();
                        waitPlayer.pop_front();
                    }else{//有,分配给第一个VIP运动员
                        temp=*i;
                        waitPlayer.erase(i);
                    }
                }
                int endTime=p.time+player[temp].processTime;//更新相关信息
                playerTable.push(PlayerTable(p.tableNum,temp,endTime));
                output(player[temp].arriveTime,p.time);
                ++table[p.tableNum].serverPlayerNum;
            }
        }
    }
    for(int i=1;i<=K;++i)
        printf("%s%d",i>1?" ":"",table[i].serverPlayerNum);
    return 0;
}

总结:

本题模拟比较复杂,做这道题之前建议先把简单一点的pat甲级1017. Queueing at Bank (25)、pat甲级1014.Waiting in Line(30)两道模拟题完成通过,如果那两道题还不能自己编代码通过的话,还是先不要做这道题了。

另外,如果总是有测试点无法通过的话,可以先到牛客网同一道题目测试一下你的代码,牛客网上如果你哪一个测试点没有通过的话,会提示你该测试点的输入数据等信息,方便你修改代码。当然pat系统和牛客网上即使是同一道题测试数据也是不一样的,你在某一个系统上AC了在另一个系统也未必能AC。例如,在牛客网上我必须将我在定义PlayerTable类中重载<运算符的函数需要定义成下面这样才能通过牛客网上的测试数据,原因在此就不多说了。

//重载<运算符
bool operator <(const PlayerTable&p)const{
    if(this->time!=p.time)
        return this->time>p.time;
    else if(this->tableNum>0&&p.tableNum>0)
        return !table[this->tableNum].vip&&table[p.tableNum].vip;
    else
        return this->tableNum>p.tableNum;
}

能看到这里的都是真爱啊,感谢大家能看完本文,祝各位编码顺利,题题AC!(づ ̄ 3 ̄)づ(づ ̄ 3 ̄)づ(づ ̄ 3 ̄)づ

你可能感兴趣的:(pat甲级)