1026 Table Tennis (30分)时间戳法

题目链接
题目不难读懂,并且不涉及什么算法,但是逻辑上非常繁琐,算法笔记上有非常详细的题意解释,这里就不赘述了。


这里主要是写我用的一种模拟时间滴答的方法,逻辑上可能要简单一些。
主要就是将题目中的时间 08:00:00 - 21:00:00 以每秒一个间隔,总共 13*60*60=46800 个时钟滴答,在每一个始终滴答到来的时候,依次处理玩家到达事件、桌子使用计时减1事件、空桌子安排玩家使用事件。
这样思考总体上逻辑简洁一些。


具体的看代码

#include<bits/stdc++.h>
using namespace std;
#define MAXPEOPLE 10005
#define MAXTABLE 105
#define TIME2SECOND(h,m,s) ((((h) - 8) * 3600) + ((m) * 60) + (s))
#define MINUTE2SECOND(m) ((m) * 60)
#define MAXTIMESTAMP 46800


int second2minute(int s)
{
	//秒数转分钟,并且舍入
	int m = s / 60;
	if ((s % 60) >= 30)
		m++;
	return m;
}

void printTime(int second)
{
	//根据秒数打印时间
	int h, m, s;
	h = second / 3600 + 8;
	second = second % 3600;
	m = second / 60;
	second = second % 60;
	s = second;
	printf("%02d:%02d:%02d ", h, m, s);
}

struct People
{
	int arrTime = 0;	//到达时间
	int needTime = 0;	//需要的时间
	int serverTime = -1;	//服务开始时间
	bool VIP = false;		//是否是VIP
	bool servered = false;	//是否已被服务
};

int n, tableNum, viptableNum, minemptytable;	//记录数、桌子总数,vip桌子总数,索引号最小的空桌子的索引
int timeStamp;	//时间戳,8:00:00为0,21:00:00为46800
int serverIndex, arriveIndex;	//当前未服务玩家的索引、当前可服务玩家索引  [serverIndex,arriveIndex)为当前时间
								//可服务但尚未服务的玩家
queue<int> VIPwaitIndex;		//在等待中的vip玩家
People playpairs[MAXPEOPLE];	//玩家记录
int tables[MAXTABLE] = { 0 };	//当前桌子还要用多久,0代表空桌子

bool isVIP[MAXTABLE] = { false };	//记录桌子是不是VIP桌
int serverNum[MAXTABLE] = { 0 };	//每个桌子服务过的人数
vector<int> VIPtable;		//记录vip桌子的索引

void dealServerIndex();
void handelTableUse()
{
	//在每一个时钟滴答到来时,将正在使用的桌子的使用时间减少1,如果减少后为0,则该桌子为空,考虑更新minemptytable
	int i;
	for (i = 0; i < tableNum; i++) {
		if (tables[i] > 0)
			tables[i]--;
		if (tables[i] == 0)
			if (i < minemptytable)	//考虑更新minemptytable
				minemptytable = i;
	}
}

void serverPeople(int peopleIndex, int tableIndex)
{
	//服务playpairs中索引号为peopleIndex的玩家,使用的桌子为tableIndex
	playpairs[peopleIndex].servered = true;	//标记已服务
	playpairs[peopleIndex].serverTime = timeStamp;	//记录服务时间
	tables[tableIndex] = playpairs[peopleIndex].needTime;	//开始桌子时间的倒计时
	serverNum[tableIndex]++;	//记录该桌子的服务人数
	while (minemptytable < tableNum && tables[minemptytable]>0) {
		//更新minemptytable,这里一定要用while,因为空桌子不是连续的
		minemptytable++;
	}
	dealServerIndex();
}

void dealServerIndex()
{
	//更新待服务玩家索引serverIndex,这里一定要用while,因为可能存在有的玩家因为VIP插队的原因已经被服务了
	while (playpairs[serverIndex].servered) {
		serverIndex++;
	}
}

void dealVIPtable()
{
	//处理空的VIP专属桌子
	for (int t : VIPtable) {
		if (tables[t] == 0 && !VIPwaitIndex.empty()) {
			//桌子为空且有VIP玩家在等待,则服务这个玩家
			int p = VIPwaitIndex.front();
			VIPwaitIndex.pop();
			serverPeople(p, t);
		}
	}
}

void handelEmptyTable()
{
	//在每一个时钟滴答,把桌子的使用倒计时-1后,处理所有的空桌子
	dealVIPtable();	//首先处理VIP桌子,因为如果等待队列中的第一人是VIP,
					//且同时存在VIP桌子和普通桌子时,优先选择VIP桌子

	//按序处理所有空桌子
	while (minemptytable < tableNum && serverIndex < arriveIndex) {
		if (playpairs[serverIndex].VIP) {
			//如果排在第一的是一个VIP玩家,则他一定没有VIP桌子可用了,所以他用了一个普通桌子,
			//因此不会在dealVIPtable()中出对,故要移出VIP等待队列
			VIPwaitIndex.pop();
		}
		serverPeople(serverIndex, minemptytable);
	}
}

void handelPeopleArrive()
{
	//在每一个时钟滴答,如果到了下一个玩家到来的时间,就将arriveIndex++,代表到达了
	//一个新的玩家,并考虑他是不是VIP,是则加入VIP等待队列
	if (playpairs[arriveIndex].arrTime == timeStamp) {
		if (playpairs[arriveIndex].VIP) {
			VIPwaitIndex.push(arriveIndex);
		}
		arriveIndex++;
	}
}

int main() {
	int h, m, s, i, need, tag;
	scanf("%d", &n);
	for (i = 0; i < n; i++) {
		//玩家信息输入
		scanf("%d:%d:%d %d %d", &h, &m, &s, &need, &tag);
		if (need > 120) {
			need = 120;
		}
		playpairs[i].arrTime = TIME2SECOND(h, m, s);
		playpairs[i].needTime = MINUTE2SECOND(need);
		if (tag == 1)
			playpairs[i].VIP = true;
	}
	scanf("%d %d", &tableNum,&viptableNum);
	for (i = 0; i < viptableNum; i++) {
		//桌子信息输入
		int v;
		scanf("%d", &v);
		VIPtable.push_back(--v);	//将桌子索引转为0开始
		isVIP[v] = true;
	}

	//按照玩家到达时间升序排列
	sort(playpairs, playpairs + n, [](People& a, People& b) {return a.arrTime < b.arrTime; });

	serverIndex = 0;
	minemptytable = 0;
	arriveIndex = 0;
	for (timeStamp = 0; timeStamp < MAXTIMESTAMP; timeStamp++) {
		//每一个时间戳,首先处理玩家到达事件,再处理桌子使用计时减1事件,最后处理
		//空桌子使用事件,三者的顺序不能颠倒
		handelPeopleArrive();
		handelTableUse();
		handelEmptyTable();
		if (serverIndex == arriveIndex == n)	
			//所有玩家都已经服务了
			break;
	}

	for (i = serverIndex; i < arriveIndex; i++) {
		//在[serverIndex,arriveIndex)中,可能存在因为VIP插队而已被服务的玩家,故要
		//把该玩家提前,这样最后在[0,serverIndex)中的就是全部服务过的玩家
		if (playpairs[i].servered) {
			playpairs[serverIndex++] = playpairs[i];
		}
	}

	//根据服务时间升序排列
	sort(playpairs, playpairs + serverIndex, [](People& a, People& b) {return a.serverTime < b.serverTime; });
	
	//答案输出
	for (i = 0; i < serverIndex; i++) {
		printTime(playpairs[i].arrTime);
		printTime(playpairs[i].serverTime);
		printf("%d\n", second2minute(playpairs[i].serverTime - playpairs[i].arrTime));
	}
	printf("%d", serverNum[0]);
	for (i = 1; i < tableNum; i++) {
		printf(" %d", serverNum[i]);
	}

	return 0;
}


/*
//input
2
08:00:00 10 1
08:05:00 10 1
3 2
2 3

//output
08:00:00 08:00:00 0
08:05:00 08:05:00 0
0 1 1
*/


/*
//input
2
20:00:00 60 0
20:30:00 10 1
1 1
1

//output
20:00:00 20:00:00 0
1
*/

最下面是算法笔记上给出的几组测试数据。
在这里插入图片描述

这种方法虽然外层循环有46800次,但是内层大部分时间只是一个简单的判断,没有实际操作,所以耗时并不高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值