对于需要模拟时间的算法题,可以将开始时间作为游戏的开始(如Unity的Start或UE的BeginPlay),每一秒的模拟作为游戏的画面更新(如Unity的Update或UE的Tick),结束时间可作为游戏的结束(如Unity的OnDestroy或UE的EndPlay)。
从这个角度来看,我们可以将PAT甲级1026 Table Tennis当成一个游戏看待,在游戏进行过程中,游戏玩家可能随时会让一位球员来打球。因此可以将可能到来的球员当成玩家输入来处理。
在每个时刻(每一秒),我们读取玩家输入,然后按照指定的游戏玩法(题目的规则)来进行游戏数据的更新。
对于游戏玩法,其要点如下:
因此按照第二点玩法我们可以给出游戏在每一秒的逻辑:
#include
using namespace std;
const int kOpenTime = 8 * 60 * 60; // 开门时间 8点
const int kCloseTime = 21 * 60 * 60; // 关门时间 21点
// 玩家类
struct Player {
int arrvTime; // 球员到达时间
int playTime; // 球员占用球桌的时间
int waitTime; // 球员等待时间
bool isVip;// 球员是不是vip
int num; // 球员的输出的编号
static const int kInvalidNum = 0xffffff;
Player(): arrvTime(kOpenTime), playTime(0), waitTime(0), isVip(false), num(kInvalidNum) {}
[[nodiscard]] bool isPlayed() const { return num != kInvalidNum; };
};
// 球桌类
struct Table {
int endTime; // 上一对球员打完球离开该球桌的时间
int id;
bool isVip;// 是不是vip桌
int serveNum; // 桌子招待过多少球员
Table(): id{-1}, endTime(kOpenTime), isVip(false), serveNum(0) {}
[[nodiscard]] bool isFree(int nowTime) const { return endTime <= nowTime; }
};
// 游戏场景中某个对象所挂载的脚本类
class TableTennisManager
{
private:
vector<Player> m_players;
// 下面两个queue内存储的是m_players内元素的指针
queue<Player *> playersWaitQueue;
queue<Player *> vipPlayersWaitQueue;
int m_unarrivedIDBegin = 0;
int m_playedCnt = 0; // 记录球员上桌的顺序
vector<Table *> m_tables_VIP;
vector<Table *> m_tables;
// 将每个时刻到来的球员当做一个输入,将该球员放入队列中让HandleQueue来处理
void HandleInput(int nowTime) {
for(int playerID = m_unarrivedIDBegin; playerID < m_players.size(); playerID++) {
Player & player = m_players[playerID];
if(player.arrvTime <= nowTime) {
m_unarrivedIDBegin++;
playersWaitQueue.emplace(&player);
if(player.isVip)
vipPlayersWaitQueue.emplace(&player);
} else {
break;
}
}
}
void HandleQueue(int nowTime, vector<Table *> & tables, queue<Player *> & waitPlayers)
{
// 清除已处理过的球员
while(!waitPlayers.empty()) {
if(waitPlayers.front()->isPlayed()) {
waitPlayers.pop();
} else {
break;
}
}
for(auto table: tables) {
if(!table->isFree(nowTime))
continue;
if(waitPlayers.empty())
break;
/* 该桌空闲,让正在等待的球员上桌 */
// 弹出等待队列,处理该球员
Player * player = waitPlayers.front();
waitPlayers.pop();
// 更新球员数据
player->waitTime = nowTime - player->arrvTime;
player->num = m_playedCnt++;
// 更新球桌数据
table->endTime = nowTime + player->playTime;
table->serveNum++;
}
}
public:
/// 游戏开始,接收游戏过程中可能需要的数据输入,模拟玩家输入并构造游戏场景————在第一次调用Update前调用
// m_players模拟玩家输入,m_tables是游戏场景所需的游戏对象
void Begin()
{
int nPlayers, nTables, nVipTables; // 球员对的数量、球桌数、VIP球桌数
// 输入在营业时间内到达的球员数据
cin >> nPlayers;
m_players.resize(nPlayers);
int nPlayers_real = 0; // 记录符合要求的输入数据(到达时间在21点之前的球员)
for(int i = 0; i < nPlayers; ++i) {
// 输入:每队球员的到达时间、占用球桌的时间(最多不能超过两小时)、是否是VIP
int h, m, s, playT, isVip;
scanf("%d:%d:%d %d %d\n", &h, &m, &s, &playT, &isVip); //NOLINT
int arvT = (h*60 + m)*60 + s; // 按照秒数记录player到达时间
// 只保留在关门前到达的球员
if(arvT < kCloseTime){
Player player;
player.arrvTime = arvT;
player.playTime = playT > 120 ? 120 * 60 : playT * 60; // 打球超过2小时的都要强制变成120分钟
player.isVip = isVip;
m_players[nPlayers_real++] = player;
}
}
m_players.resize(nPlayers_real);
// 输入球桌数据
cin >> nTables >> nVipTables;
m_tables.resize(nTables);
for(int i = 0; i < nTables ; i++) {
m_tables[i] = new Table{};
}
// 输入VIP球桌数据
for(int i = 0; i < nVipTables; ++i) {
int tableID;
cin >> tableID;
tableID--;
m_tables[tableID]->isVip = true; // 标记vip桌
m_tables[tableID]->id = tableID;
m_tables_VIP.push_back(m_tables[tableID]);
}
// 按照球员到达时间排序
sort(m_players.begin(), m_players.end(), [](Player &a, Player &b){
return a.arrvTime < b.arrvTime;
});
}
/// 更新游戏数据,更新游戏场景数据(m_players和m_tables)————每个时刻(每秒)调用一次
void Update(int nowTime)
{
// 处理该时刻可能到达的球员
HandleInput(nowTime);
// 在VIP球桌上处理VIP球员
HandleQueue(nowTime, m_tables_VIP, vipPlayersWaitQueue);
// 在所有球桌上处理所有球员(上一步因为VIP球桌已满而未能进入VIP球桌的VIP球员将会被视为普通球员处理)
HandleQueue(nowTime, m_tables, playersWaitQueue);
}
/// 游戏结束,输出游戏执行结果————在最后一次Update执行完调用
void End()
{
// 将球员按照打球的顺序进行排序
sort(m_players.begin(), m_players.end(), [](Player &a, Player &b){
return a.num < b.num;
});
// 输出球员的打球信息
for(Player &player: m_players) {
if(player.num == 0xffffff) continue; // 超过21点才能打球的球员就不能输出了
int ah = player.arrvTime/3600, am = player.arrvTime % 3600 / 60, as = player.arrvTime % 60;
int bh = (player.arrvTime + player.waitTime) / 3600, bm = (player.arrvTime + player.waitTime) % 3600 / 60, bs = (player.arrvTime + player.waitTime) % 60;
// 等待的分钟数要四舍五入
player.waitTime = player.waitTime/60 + (player.waitTime%60 >= 30);
printf("%02d:%02d:%02d %02d:%02d:%02d %d\n",ah,am,as,bh,bm,bs,player.waitTime);
}
// 输出球桌的接待信息
for(int i = 0; i < m_tables.size(); ++i) {
cout << m_tables[i]->serveNum << (i == m_tables.size() - 1 ? "\n" : " ");
delete m_tables[i];
}
}
};
int main() {
TableTennisManager tennisManager;
tennisManager.Begin();
for (int nowTime = kOpenTime; nowTime < kCloseTime; nowTime++) {
// 每秒调用一次,对相关数据进行更新
tennisManager.Update(nowTime);
}
tennisManager.End();
return 0;
}