题目大意
k张桌子,玩家到达后总是选择编号最小的桌子。如果训练时间超过2h会被压缩成2h,如果到达时候没有球桌空闲就变成队列等待。 k张桌子中m张是vip桌,如果vip桌子有空闲,而且队列里面有vip成员,那么等待队列中的第一个vip球员会到最小的vip球桌训练。如果vip桌子空闲但是没有vip来,那么就分配给普通的人。如果没有vip球桌空闲,那么vip球员就当作普通人处理,也就是先来先服务。现在给出每个球员的到达时间、要玩多久、是不是vip(是为1不是为0)。给出球桌数和所有vip球桌的编号,求出在关门前所有得到桌子的球员的到达时间、训练开始时间、等待时长(取整数,四舍五入),营业时间为8点到21点。如果在21:00之前没有排的上队,那么就不需要服务。
思路解析
算是PAT中比较麻烦的题目了。本题的排队模型是“非紧凑的”,对于非紧凑的排队问题,同样只关心队头元素,特殊情况,比如本题中的VIP情况,顶多让他插个队,提前处理。目前也只有这种方法可以达到最优解,笔者尝试了几种不同方案(末尾会给出,大家可以对比一下)均会超时,顶多拿到19分。
具体思路如下:将玩家按到达的时间升序排序。用一个指针i选中当前队列中的第一个人,另一个指针vipid指向队列中等待的第一个VIP(为了给VIP开后门用),选出最先服务完的桌子。
如果是会员桌子,那就一定要尽量保证“物尽其用”,尽量服务VIP,先检查vipid,如果他在当前等待队列中,(也就是说在桌子服务完之前就已经在等待了),那么桌子分配给他;没有会员就分配给普通玩家。
如果是普通桌子,就要先来先服务,但是如果最先来的这个人(也就是i指针指向的这个人)是个VIP,就一定要保障他的会员权益,先检查会员桌子是不是就绪(也就是在他来之前就已经空闲了),如果是的话,就分配给他会员桌子;没有会员桌子的话,再把他当做普通人分配普通桌子。流程图如下:
示例代码
#include<iostream>
#include<vector>
#include<algorithm>
#include<math.h>
using namespace std;
struct person{
public:
int come, get,play,wait;
bool isvip = false;
}pperson;
struct table{
public:
bool isvip = false;
int present = 8 * 3600,num = 0;
};
vector<person> vec,res;//res存放被服务的人
vector<table> tables;//角标就是编号
int getVIP(int index) {
index++;
while (index < vec.size() && vec[index].isvip == false) index++;
return index;
}
void alloac(int p, int t) {
vec[p].get = tables[t].present < vec[p].come ? vec[p].come : tables[t].present;
if (vec[p].get < 21 * 3600) {
res.push_back(vec[p]);
tables[t].num++;
tables[t].present = vec[p].get + vec[p].play;
}
}
bool cmp(person p1,person p2) {
return p1.come < p2.come;
}
bool cmp2(person p1, person p2) {
return p1.get < p2.get;
}
int main() {
int n,k,p;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
int hour, min, sec, play, flag;
scanf("%d:%d:%d %d %d", &hour, &min, &sec, &play, &flag);
pperson.come = hour * 3600 + min * 60 + sec;
pperson.get = 21 * 3600;
if (pperson.come >= 21 * 3600) continue;
pperson.play = play > 120 ? 120 * 60 : play * 60;
pperson.isvip = (flag == 1 ? true : false);
vec.push_back(pperson);
}
scanf("%d %d", &k,&p);
tables.resize(k + 1);
for (int i = 0; i < p; i++) {
int temp;
scanf("%d", &temp);
tables[temp].isvip = true;
}
sort(vec.begin(), vec.end(), cmp);
int i = 0;
int vipid = getVIP(-1);
while (i < vec.size()) {
int min = 999999999, index = -1;
for (int j = 1; j < tables.size(); j++) {
if (tables[j].present < min) {
min = tables[j].present;
index = j;
}
}
if (tables[index].present >= 21 * 3600) break;
if (vec[i].isvip == true && i < vipid) {
i++;
continue;
}
if (tables[index].isvip == true) {//会员桌子
//先检查会员 插队
if (vec[i].isvip == true) {//队头就是会员
alloac(i, index);
vipid = getVIP(vipid);
i++;
}
else {
if (vipid < vec.size() && vec[vipid].come <= tables[index].present) {//看看队里有没有会员
alloac(vipid, index);
vipid = getVIP(vipid);
}
else {//没有会员
alloac(i, index);
i++;
}
}
}
else {//普通桌子
int mintemp = 999999999; int tempindex = -1;
if (vec[i].isvip == true) {//第一个人是VIP
for (int k = 1; k < tables.size(); k++) {
if (tables[k].isvip && tables[k].present < mintemp) {
mintemp = tables[k].present;
tempindex = k;
}
}
if (tempindex != -1 && tables[tempindex].present <= vec[i].come) {//有会员桌子 插队
alloac(i, tempindex);
vipid = getVIP(vipid);
i++;
}
else {//没有会员桌子 那就跟普通人一样
alloac(i, index);
vipid = getVIP(vipid);
i++;
}
}
else {//普通人
alloac(i, index);
i++;
}
}
}
sort(res.begin(), res.end(), cmp2);
for (int i = 0; i < res.size(); i++) {
printf("%02d:%02d:%02d ", res[i].come / 3600, res[i].come % 3600 / 60, res[i].come % 60);
printf("%02d:%02d:%02d %.0f\n", res[i].get / 3600, res[i].get % 3600 / 60, res[i].get % 60,round((res[i].get - res[i].come)/60.0));
}
printf("%d", tables[1].num);
for (int i = 2; i < tables.size(); i++)
printf(" %d", tables[i].num);
return 0;
}
这里是其他方案,注意:不是满分的,但是是正确的,有多个超时点
#include<iostream>
#include<vector>
#include<algorithm>
#include<math.h>
using namespace std;
struct person{
public:
int id,come, play, isvip, hour, min, sec, wait, get;//isvip为1表示会员
bool isser = false;//初始化为未服务
}temperson;
struct table{
public:
bool isvip = false;//初始化为普通桌子
int present = 8 * 3600,num = 0;
}temptable;
bool cmp(person p1, person p2) {
return p1.come < p2.come;
}
bool cmp3(person n1, person n2) {
return n1.get < n2.get;
}
int n, k, m;
vector<table> tables;
vector<person> vec;
vector<person> res;
void alloact(int p,int t) {
vec[p].get = vec[p].come < tables[t].present ? tables[t].present : vec[p].come;
vec[p].wait = vec[p].get - vec[p].come;
vec[p].isser = true;
if (vec[p].get < 21 * 3600) {
tables[t].present = vec[p].come + vec[p].play;
tables[t].num++;
res.push_back(vec[p]);
}
}
int main() {
scanf("%d", &n);
vec.resize(n);
for (int i = 0; i < n; i++) {//读人
scanf("%d:%d:%d %d %d", &vec[i].hour, &vec[i].min, &vec[i].sec, &vec[i].play, &vec[i].isvip);
vec[i].come = vec[i].sec + vec[i].min * 60 + vec[i].hour * 3600;
vec[i].play = vec[i].play <= 120 ? vec[i].play * 60 : 120 * 60;
}
scanf("%d %d", &k, &m);
tables.resize(k+1);
for (int i = 0; i < m; i++) {//执行m次,读取并统计会员桌子
int temp;//桌子id
scanf("%d", &temp);
tables[temp].isvip = true;
}
int count = 0;//计数器。统计处理人数 当到最后一个人时说明处理完了
sort(vec.begin(), vec.end(), cmp);
for (int i = 0; i < vec.size(); i++) {
vec[i].id = i;
}
int pre = -1;
while (count <= vec.size() - 1) {
//选出最早服务完的桌子
int index = -1;
int min = 21*3600;//指向最早服务完的这个桌子,而且保证如果时间相同一定指向编号最小的那一个
for (int i = 1; i < k + 1; i++) {
if (tables[i].present < min) {
index = i;
min = tables[i].present;
}
}
if (index == -1) //说明九点之前任何桌子都没有服务完,本来就要加班了 还有人要来 果断拒绝
break;
//将当前时间到来之前的人全部放进来
vector<person> temp;
int vipid = -1;//指向第一个VIP的下标
for (int i = pre + 1; i < vec.size(); i++) {
if (!vec[i].isser && vec[i].come <= tables[index].present) {
temp.push_back(vec[i]);
if (vec[i].isvip == 1 && vipid == -1) {
vipid = i;
}
}
else if (vec[i].come > tables[index].present) {
break;
}
}
if (temp.size() == 0) {//生意不好,没人在等
//找下一个人
for (int i = pre + 1; i < vec.size(); i++) {
if (!vec[i].isser) {
for (int j = 1; j < k + 1;j++) {
if(tables[j].present < vec[i].come)
tables[j].present = vec[i].come;
}
break;
}
}
continue;
}
if (tables[index].isvip) {//如果这个桌子是会员桌子
if (vipid != -1) {//有会员在等待
alloact(vipid, index);//直接把会员桌子分配给他
count++;
}
else {//没有会员就给第一个人
alloact(temp[0].id, index);
pre = temp[0].id;
count++;
}
}
else {//如果是普通桌子
if (vipid != -1) {
//一定要保障会员的权益,尽管这个桌子是普通桌子,要看一看是不是同时有得出空来的会员桌子
for (int i = 1; i < k + 1; i++) {
if (tables[i].present == tables[index].present && tables[i].isvip) {
alloact(vipid, i);
count++;
break;
}
}
}
else {
alloact(temp[0].id, index);//分配给第一个人
pre = temp[0].id;
count++;
}
}
}
sort(res.begin(), res.end(), cmp3);
for (int i = 0; i < res.size(); i++) {
int a = res[i].get / 3600;
int b = (res[i].get - a * 3600) / 60;
int c = res[i].get - (a * 3600 + b * 60);
printf("%02d:%02d:%02d %02d:%02d:%02d %.0f\n", res[i].hour, res[i].min, res[i].sec, a, b, c, round(res[i].wait / 60.0));
}
printf("%d", tables[1].num);
for (int i = 2; i < tables.size(); i++) {
printf(" %d", tables[i].num);
}
return 0;
}
总结
对于非紧凑的排队问题,同样只关心队头元素,没有特殊情况就不关心后面的,一定是每次只处理队头元素, 特殊情况顶多让后面的插个队,不可以同时将多个元素划分到一起,一方面处理难度加大,另一方面耗时增加。 核心思想是让桌子/柜台去选人,另外一定要注意present = get + play 这一句是提醒我自己的……手动狗头???