WEEK10 周记 模拟限时赛——模拟题_团队聚会

本文解析了一项关于团队聚会时间安排的算法挑战,通过贪心算法和暴力遍历两种方法,详细阐述了如何在复杂的个人日程中寻找共同空闲时段,适用于多人协作场景。

WEEK10 周记 模拟限时赛——模拟题_团队聚会

一、题意

1.简述

TA团队每周都会有很多任务,有的可以单独完成,有的则需要所有人聚到一起,开过会之后才能去做。但TA团队的每个成员都有各自的事情,找到所有人都有空的时间段并不是一件容易的事情。

给出每位助教的各项事情的时间表,你的任务是找出所有可以用来开会的时间段。

2.输入格式

在这里插入图片描述

3.输出格式

在这里插入图片描述

4.样例

Input
2
3
3
2020 06 28 15 00 00 2020 06 28 18 00 00 TT study
2020 06 29 10 00 00 2020 06 29 15 00 00 TT solving problems
2020 11 15 15 00 00 2020 11 17 23 00 00 TT play with his magic cat
4
2020 06 25 13 30 00 2020 06 25 15 30 00 hrz play
2020 06 26 13 30 00 2020 06 26 15 30 00 hrz study
2020 06 29 13 00 00 2020 06 29 15 00 00 hrz debug
2020 06 30 13 00 00 2020 06 30 15 00 00 hrz play
1
2020 06 01 00 00 00 2020 06 29 18 00 00 zjm study
2
1
1800 01 01 00 00 00 2200 01 01 00 00 00 sleep
0
Output
Scenario #1:
appointment possible from 01/01/1800 00:00:00 to 06/25/2020 13:30:00
appointment possible from 06/25/2020 15:30:00 to 06/26/2020 13:30:00
appointment possible from 06/26/2020 15:30:00 to 06/28/2020 15:00:00
appointment possible from 06/28/2020 18:00:00 to 06/29/2020 10:00:00
appointment possible from 06/29/2020 15:00:00 to 01/01/2200 00:00:00

Scenario #2:
no appointment possible

二、算法

主要思路

思路一:贪心

构造一个结构体point,存储时间点的相关信息,包括年月日小时分钟秒,还要计算出改时间点距离“公元原点”——1800/01/01 00:00:00有多少秒。这个时间点可能是起点也可能是终点。当然一些大于小于号等的重载还是必要的,这点不再赘述。
构造一个结构体seg,存储时间段的相关信息。

struct seg{
	point st,ed;   //起点,终点
	int ta;        //助教的编号
	bool operator<(const seg& b)const{  //须重载小于号否则报错 
		return st>b.st;
	}
	seg(point st,point ed,int ta):st(st),ed(ed),ta(ta){}
};

贪心的主要思路是:

  • 将所有输入的时间段按照起点从小到大的顺序排列起来(可以通过压入小根堆来实现)。
  • 将const point类型的te(代表“公元终点——”2200/01/01 00:00:00)压入到小根堆中,这种预处理是为了到最后能够更方便地退出循环。
  • 初始化 l l l d d dd dd为1800/01/01 00:00:00,这两个时间点变量将框定一个时间段,这个时间段具有这样的属性:已经有一个助教在这个时间段有任务了(这个任务的截止时间就是 d d dd dd),对于TA数=2的情况来说这个时间段是不符合条件的,但是对于TA数大于2的情况这个时间段或者其中的一部分却是可能符合条件的,只要没有第二个TA在这个时间段(或其中的一部分)上有任务即可。定义变量 t a ta ta存储当前这个时间段是谁在有任务,定义变量 o u t s outs outs o u t e oute oute存储 l l l d d dd dd前面的满足要求的但还不能输出的时间段(要输出就得输出一长串,这里 o u t s outs outs o u t e oute oute是暂存前面已经确定的部分,等全部确定之后再输出),初始时 o u t s outs outs o u t e oute oute均为tag——1800/01/01 00:00:00。
  • 进入循环,从堆中取出一个时间段 o t h e r other other,用 o t h e r other other的相关属性跟 l l l d d dd dd进行如下比较,
    • 一种特殊的情况,两个时间段的助教是相同的,那么就需要合为一段。具体方法见代码,进入下一次循环。
    • o t h e r . s t > = d d other.st>=dd other.st>=dd,此时说明下一段与当前段之间空出了一个没有任何人有任务的时间段 d d dd dd o t h e r . s t other.st other.st(当然如果 o t h e r . s t = d d other.st=dd other.st=dd这个时间段的长度是0),那么对于TA数大于2的情况来说,从 l l l o t h e r . s t other.st other.st都是符合条件的,我们就可以与前面的缓存 o u t s outs outs o u t e oute oute时间段进行拼接,新的缓存时间段保存在 o u t s outs outs o u t e oute oute中,同时也要将 l l l d d dd dd修改为下一个时间段的起点和终点;对于TA数=2的情况,只有 d d dd dd o t h e r . s t other.st other.st是符合要求的,直接输出即可,进入下一次循环。
    • o t h e r . s t ≥ l 且 o t h e r . s t < d d other.st \ge l且other.st<dd other.stlother.st<dd, 此时对于TA数大于2的情况来说,从l到other.st都是符合条件的,我们就可以与前面的缓存 o u t s outs outs o u t e oute oute时间段进行拼接,然后直接输出(不需要再缓存了),同时也需要将 l l l d d dd dd进行相应修改;对于TA数=2的情况,则是不符合要求。进入下一次循环。
    • o t h e r . s t < l 且 o t h e r . e d > l other.st<l且other.ed>l other.st<lother.ed>l,这种情况只可能出现在前一种情况处理之后。我们只需要将这一个时间段按照 l l l o t h e r . e d other.ed other.ed来处理即可。这种情况不需要输出,只需要修改 l l l d d dd dd。进入下一次循环。
  • 直到小根堆为空循环才停止(一开始小根堆至少有一个元素在里面,所以一开始必然会进入到循环中)。
  • 循环中所有的输出都要检查输出段是否满足长度大于1h,只有满足才输出。
  • 最后循环结束,还要检查 o u t s outs outs o u t e oute oute这个时间段是不是还有缓存没有输出。
  • 对于一次都没有输出的情况进行相应的输出。
  • 算法结束。

这一算法的时间复杂度为 O ( m ) O(m) O(m),也即所有任务的个数。
所以总的数量级在 100 × 20 × 100 = 2 e 7 100\times20\times 100=2e7 100×20×100=2e7


思路二:暴力遍历,滑动窗口形态

大体的思路如下:
将所有时间点(包括起点和终点)按照先后顺序排列在时间轴上,遍历每一个小的时间段(临近两个时间点形成的),检查是否满足要求。当然具体处理的时候还要注意要将相邻的满足要求的时间段拼在一块。
检查的方法是遍历所有人的任务时间段(用一个一维vector<pair<point,point> >数组存一下,维度代表助教,<point,point>代表<起点,终点>),看看是否有时间段会覆盖这个小时间段。具体的检查思路见代码check函数和not_task函数。
这样的时间复杂度大一些。总的数量级为 20 × 100 × 4000 × 100 = 8 e 8 20\times100\times4000\times100=8e8 20×100×4000×100=8e8(粗略估计),不过还是可以AC的。
可以参考博客:学长博客


体会

这个题按第一种思路改了多次,总是WA,后来发现原来原先某一次改动并不正确,实际上样例都过不了,把这个点改掉之后就AC了= =
所以改动之后一定要重新算一算样例是不是能过!!!!!

三、代码

思路一代码:
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct point{
	long long int y,m,d,h,mm,s; //得是long long 为了后面能算lth 
	long long lth;
	point(int yy=1800,int mt=1,int dd=1,int hh=0,int mmm=0,int ss=0):y(yy),m(mt),d(dd),h(hh),mm(mmm),s(ss){
		lth = (y-1800)*360*24*3600 + (m-1)*30*24*3600 + (d-1)*24*3600 + h*3600 + mm*60 + s; 	//这里必须把参数改成mt,否则会认错 
	}//可能如果参数和成员变量的名字相同,函数里用参数 
	bool operator<(const point& b)const{
		return lth<b.lth;
	}
	bool operator>(const point& b)const{
		return lth>b.lth;
	}
	bool operator<=(const point& b)const{
		return lth<=b.lth;
	}
	bool operator>=(const point& b)const{
		return lth>=b.lth;
	}
	bool operator==(const point& b)const{
		return lth==b.lth;
	}
	bool operator!=(const point& b)const{
		return lth!=b.lth;
	}
};
struct seg{
	point st,ed;
	int ta;
	bool operator<(const seg& b)const{  //须重载小于号否则报错 
		return st>b.st;
	}
	seg(point st,point ed,int ta):st(st),ed(ed),ta(ta){}
};

const point tag;
const point te = point(2200,1,1,0,0,0);
int cnt = 0;

void s2n(point& p){
	char str[6][200];
	int y,m,d,h,mm,s;
	scanf("%s",str[0]);
	sscanf(str[0],"%d",&y);
	scanf("%s",str[1]);
	sscanf(str[1],"%d",&m);
	scanf("%s",str[2]);
	sscanf(str[2],"%d",&d);
	scanf("%s",str[3]);
	sscanf(str[3],"%d",&h);
	scanf("%s",str[4]);
	sscanf(str[4],"%d",&mm);
	scanf("%s",str[5]);
	sscanf(str[5],"%d",&s);
	p = point(y,m,d,h,mm,s);  //赋值给p 
}
void output(point t0,point t1){
	cnt++;
	printf("appointment possible from ");
	printf("%02lld/%02lld/%04lld %02lld:%02lld:%02lld to",t0.m,t0.d,t0.y,t0.h,t0.mm,t0.s);
	printf(" %02lld/%02lld/%04lld %02lld:%02lld:%02lld\n",t1.m,t1.d,t1.y,t1.h,t1.mm,t1.s);
}
priority_queue<seg> p;
int main(){
	//freopen("data.txt","r",stdin);
	int t;
	scanf("%d",&t);
	for(int tt=0;tt<t;tt++){
		int taNum;
		scanf("%d",&taNum);
		 
		for(int m=0;m<taNum;m++){
			int tnum;
			scanf("%d",&tnum);
			for(int i=0;i<tnum;i++){
				point s,e;  //起点,终点 
				s2n(s);
				s2n(e);
				while(getchar()!='\n'){}; //处理任务信息 
				if(s!=e) 
					p.push(seg(s,e,m)); //防备加入起点和终点相同的时间段 
			}
		}
		
		printf("Scenario #%d:\n",tt+1);
		p.push(seg(te,te,-1)); 
		cnt = 0;
		point l,dd; int ta=-1;  //框定当前的可能可以开会的时间段 
		point outs,oute;
		
		while(!p.empty()){ 
			seg other = p.top(); //下一个时间段 
			p.pop();
			if(other.st>=dd){//空出一个段或者紧密衔接 
				if(taNum>2){ //不管l到other.st是不是 
					outs = (oute==l)?outs:l;  
					oute = other.st;
				}
				else{ //tanum=2 
					if(other.st.lth-dd.lth>=3600){
						output(dd,other.st); //空段输出
						outs = tag; oute = tag;
					}
				}
				l = other.st;
				dd = other.ed;
				ta = other.ta;
				continue;
			} 
			
			if(other.ta==ta){//两个段的人是相同的,合为一段
				dd = dd>other.ed?dd:other.ed;
				continue;
			}
			
			if(other.st<l){//另一个助教的任务,且起点小于l,终点大于l  此时outs和oute必然是无效的,也就是前面的已经输出完了 
				if(other.ed>l){ //只需要更改l和dd和ta,不需要输出 
					//p.push(seg(l,other.ed,other.ta));   
					l = other.ed<dd?other.ed:dd;
					dd = other.ed<dd?dd:other.ed;
					ta = other.ed<dd?ta:other.ta; 
				}
					
				continue;
			}
			
			//此时other.st>=l&&other.st<dd 
			if(taNum>2){
				if(oute==l){  //前面还有需要输出的 
					if(other.st.lth-outs.lth>=3600){ 
						output(outs,other.st);
						outs = tag; oute = tag;
					}
				}
				else{ // 前面没有需要输出的 
					if(other.st.lth-l.lth>=3600){ 
						output(l,other.st);	
						outs = tag; oute = tag;	
					}
				}		
			} 	
			l = other.ed<dd?other.ed:dd;
			dd = other.ed<dd?dd:other.ed;
			ta =  other.ed<dd?ta:other.ta;
		}
		if(oute==te){  //避免最后输出2200 01 01 to 2200 01 01 
			if(oute.lth-outs.lth>=3600)
				output(outs,oute);
		}
		
		if(!cnt) printf("no appointment possible\n");
		printf("\n");  //额外的空行 
	}
	//freopen("CON","r",stdin);
	return 0;
}
思路二代码:
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector> 
using namespace std;
struct point{
	long long int y,m,d,h,mm,s; //得是long long 为了后面能算lth 
	long long lth;
	point(int yy=1800,int mt=1,int dd=1,int hh=0,int mmm=0,int ss=0):y(yy),m(mt),d(dd),h(hh),mm(mmm),s(ss){
		//printf("%lld %lld %lld %lld %lld %lld",(y-1800)*360*24*3600,(m-1)*30*24*3600,(d-1)*24*3600,(d-1)*24*3600,h*3600,s);
		lth = (y-1800)*360*24*3600 + (m-1)*30*24*3600 + (d-1)*24*3600 + h*3600 + mm*60 + s; 	//这里必须把参数改成mt,否则会认错 
	}//可能如果参数和成员变量的名字相同,函数里用参数 
	bool operator<(const point& b)const{
		return lth<b.lth;
	}
	bool operator>(const point& b)const{
		return lth>b.lth;
	}
	bool operator<=(const point& b)const{
		return lth<=b.lth;
	}
	bool operator>=(const point& b)const{
		return lth>=b.lth;
	}
	bool operator==(const point& b)const{
		return lth==b.lth;
	}
	bool operator!=(const point& b)const{
		return lth!=b.lth;
	}
};

int taNum;  //助教人数 
const point ts(1800,1,1,0,0,0);
const point te = point(2200,1,1,0,0,0);
set<point> s;
vector<point> vp;
vector<pair<point,point> > ta[30],ans;

void s2n(point& p){
	char str[6][200];
	int y,m,d,h,mm,s;
	scanf("%s",str[0]);
	sscanf(str[0],"%d",&y);
	scanf("%s",str[1]);
	sscanf(str[1],"%d",&m);
	scanf("%s",str[2]);
	sscanf(str[2],"%d",&d);
	scanf("%s",str[3]);
	sscanf(str[3],"%d",&h);
	scanf("%s",str[4]);
	sscanf(str[4],"%d",&mm);
	scanf("%s",str[5]);
	sscanf(str[5],"%d",&s);
	p = point(y,m,d,h,mm,s);  //赋值给p 
}

void output(point t0,point t1){
	printf("appointment possible from ");
	printf("%02lld/%02lld/%04lld %02lld:%02lld:%02lld to",t0.m,t0.d,t0.y,t0.h,t0.mm,t0.s);
	printf(" %02lld/%02lld/%04lld %02lld:%02lld:%02lld\n",t1.m,t1.d,t1.y,t1.h,t1.mm,t1.s);
}

bool not_task(int id,point l,point r){
	for(int i=0;i<ta[id].size();i++)
		if(ta[id][i].first<=l&&ta[id][i].second>=r) return 0;
	return 1;
}
bool check(point l,point r){  //检查该时间段是否满足要求中的前两条 
	int cnt = 0;
	for(int i=1;i<=taNum;i++)
		if(not_task(i,l,r)) cnt++;
	return (cnt>=taNum-1)&&(cnt>=2);
}
void clr(int n){
	s.clear();
	ans.clear();
	vp.clear();
	for(int i=0;i<=n;i++) ta[i].clear();
}
int main(){
	//freopen("data.txt","r",stdin);
	int t;
	scanf("%d",&t);
	for(int tt=0;tt<t;tt++){
		scanf("%d",&taNum);
		clr(taNum);
		s.insert(ts);
		s.insert(te);
		for(int m=1;m<=taNum;m++){
			int tnum;
			scanf("%d",&tnum);
			for(int i=0;i<tnum;i++){
				point ps,pe;  //起点,终点 
				s2n(ps);
				s2n(pe);
				while(getchar()!='\n'){}; //处理任务信息 
				ta[m].push_back(make_pair(ps,pe));
				s.insert(ps);
				s.insert(pe); 
			}
		}
		
		printf("Scenario #%d:\n",tt+1);
		for(set<point>::iterator it=s.begin();it!=s.end();it++) vp.push_back(*it);  //将set中的信息整理出来 
		int l = 0,r = 0,pnum =vp.size();
		while(l<pnum&&r<pnum){ 
			while(r<pnum-1&&check(vp[r],vp[r+1])) r++;
			if(vp[r].lth-vp[l].lth>=3600) ans.push_back(make_pair(vp[l],vp[r]));
			l = r+1;
			while(l<pnum-1 && !check(vp[l],vp[l+1])) l++;
			r = l;//r = l+1? 
		}
		if(ans.size()==0) printf("no appointment possible\n");
		else{
			for(int i=0;i<ans.size();i++)
				output(ans[i].first,ans[i].second);
		}
		printf("\n");  //额外的空行 
	}
	//freopen("CON","r",stdin);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值