[week10]T3——团队聚会

题意

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

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

Input

第一行一个数T(T≤100),表示数据组数。

对于每组数据,第一行一个数m(2 ≤ m ≤ 20),表示TA的数量。

对于每位TA,首先是一个数n(0≤ n≤100),表示该TA的任务数。接下来n行,表示各个任务的信息,格式如下

YYYY MM DD hh mm ss YYYY MM DD hh mm ss “some string here”

每一行描述的信息为:开始时间的年、月、日、时、分、秒;结束时间的年、月、日、时、分、秒,以及一些字符串,描述任务的信息。

数据约定:

所有的数据信息均为固定位数,位数不足的在在前面补前导0,数据之间由空格隔开。

描述信息的字符串中间可能包含空格,且总长度不超过100。

所有的日期时间均在1800年1月1日00:00:00到2200年1月1日00:00:00之间。

为了简化问题,我们假定所有的月份(甚至2月)均是30天的,数据保证不含有不合法的日期。

注意每件事务的结束时间点也即是该成员可以开始参与开会的时间点。

Output

对于每一组数据,首先输出一行"Scenario #i:",i即表明是第i组数据。

接下来对于所有可以用来开会的时间段,每一个时间段输出一行。

需要满足如下规则:

在该时间段的任何时间点,都应该有至少两人在场。
在该时间段的任何时间点,至多有一位成员缺席。
该时间段的时间长度至少应该1h。
所有的成员都乐意一天24h进行工作。

举个例子,假如现在TA团队有3位成员,TT、zjm、hrz。

那么这样的时间段是合法的:会议开始之初只有TT和zjm,后来hrz加入了,hrz加入之后TT离开了,此后直到会议结束,hrz和zjm一直在场。

要求:

输出满足条件的所有的时间段,尽管某一段可能有400年那么长。
时间点的格式为MM/DD/YYYY hh:mm:ss。
时间段的输出格式为"appointment possible from T0 to T1",其中T0和T1均应满足时间点的格式。
严格按照格式进行匹配,如果长度不够则在前面补前导0。
按时间的先后顺序输出各个时间段。
如果没有合适的时间段,输出一行"no appointment possible"。
每组数据末尾须打印额外的一行空行。

输入样例

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

输出样例

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

提示


分析

这是目前为止本菜鸡认为最难的T3模拟题


  • 题目分析

题目本身倒是不复杂,意图明显:在给出的固定时间段中,选出所有能安排会议的时间区段并输出。

选择标准:

  • 该时段内只有一个或没有人有任务
  • 若该时段内有一个人离开,则总人数不能少于3
  • 选择的时间段至少为1h

这道题的难点在于确定时间段和时间差的计算。


  • 解决思路

在解决过程中会发现,给出的输入数据中,每个时间段之后的人员名字、任务内容都是无用的。因此不必要占用内存来存储这些信息,可以直接忽略。

  • 选择时间段

这道题在思考的时候会发现其实很像区间选点这样的类型。所以在最开始我就想按照类似的做法来解决。

也就是把每个时间段看作一段区间,将它们的起点和终点作为左右端点投射到总时间段区间上。再从总区间的起点开始,依次遍历每个时间点,判断当前时间点是否可以作为一段可选择时间区间的起点或终点。将一段选择区间的起点和终点进行存储。

  • 如何判断当前时间点的状态呢?

1)如何表示时间?

一个时间点上是否能选择,关键在于该时间段前后不能参会人数的变化。所以进一步,将每一个端点看作独立的一个时间点,在这个时间点前后参会人数会发生变化。而每个时间点都存储在该时间点,将会变化的离开会议人数。

在每个任务时间段的开始,就代表着该点处离开人数将会+n(n为该时间点的个数,因为可能存在时间点会重复),反之,在一个任务时间段的结束点,代表着离开人数将会-m。

在存储的时候,只需要在输入时存储每个时间点对应的离开人数即可,而不需要存储该时间点的类型。因为一个时间点可能同时是开始点或结束点。同样,要使用一个集合,不重复地记录每一个出现的时间点。

2)如何选择时间点?

分析时间点可以发现,当一个时间点之后,离开人数从大于1减少为1或0时,该点可以作为一个选择时间段的起始;而当一个时间点后,离开人数从0或1变为大于1时,这个点即为一个选择时间段的结束。

在遍历过程过有点类似于尺取法,不断更新和标记左右段点。在最后一个时间点前,只要出现一个右端点,代表一定已经出现了一个左端点,此时即可存储这段时间点。存储后将左端点清零,直到下一次出现再进行标记。

难点在于最后一个时间点。在最后一个时间点到总区间结尾需要另外进行单独判断。只要存在有效的左端点,就说明到总区间结尾可能存在最后一段可选择时间。若在这段区间内,离开人数符合要求,则从最后一个左指针到结尾是一段可选择区间。

3)如何计算时间差?

最开始我尝试对两个时间做减法,比较它们的差值。但是我发现非常麻烦。于是我突然想到可以反向思考,也就是对两个时间段中较小的一个做加法,这比减法容易多了。然后将加法后的结果与较大的进行比较,若加法后仍小于等于较大时间,说明这段时间一定至少有1h,否则就没有。

加法的思路:

从小时开始加1。分秒不变,小时遇到24进位归0,日遇到31进位归1,月遇到13进位归1,年遇到进位+1。


问题

  • 存储时间点

最开始的问题就在于存储时间点。刚开始的思路是用已经存储的人数来判断该点是否已经被存储过。但是这个想法存在bug,若一个点出现了一次作为起始,第二次作为结束,那么这个点就归零了。类似的想法都是有漏洞的。于是干脆改成了bool数组判断。其实可以直接用set来存储去重,但是set没法排序,还需要再复制到vector中进行排序。

  • 时间点的选择规律

最开始左指针的情况我只考虑了离开人数从大于1减少到1的情况。可是我的右指针却两种情况又都考虑到了【🧠❓】

实际上在区间上,显然从一个左指针往后遍历,可以包容离开人数从0变为1或1变为0的变化,其他的情况都说明遇到了右指针。同样,从右指针往后,只能包容人数从大于1变为小于等于1。

但是所有离开人数变为1的时间点合法,都建立在总人数大于2的基础上。

  • 输出格式

我最开始的输出格式一直是自己写的补零。但是最后发现,这道题必须规范为输出流对齐补零。


总结

  1. 呜哇哇哇哇哇哇改的要落泪了😭还是缺乏这种对较复杂思路和代码的调试能力

代码

//
//  main.cpp
//  lab2
//
//

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iomanip>
#include <map>
using namespace std;

enum date_name{year,month,day,hour,minute,sec};

struct Date             //时间类型
{
    int date[6];        //年月日时分秒
    
    void initialize(int y,int inf[])     //初始化时间
    {
        date[year] = y;
        
        for( int i = 0 ; i < 5 ; i++ )     //除去year以外的时间
            date[i + 1] = inf[i];
    }
    
    bool operator < (const Date& d) const       //排序
    {
        if( date[year] != d.date[year] )
            return date[year] < d.date[year];
        else if( date[month] != d.date[month] )
            return date[month] < d.date[month];
        else if( date[day] != d.date[day] )
            return date[day] < d.date[day];
        else if( date[hour] != d.date[hour] )
            return date[hour] < d.date[hour];
        else if( date[minute] != d.date[minute] )
            return date[minute] < d.date[minute];
        
        return date[sec] < d.date[sec];
    }
    
    bool operator == (const Date& d) const
    {
        if( date[year] == d.date[year]  && date[month] == d.date[month] && date[day] == d.date[day] && date[hour] == d.date[hour]  && date[minute] == d.date[minute] && date[sec] == d.date[sec])
            return true;
        return false;
    }
    
    bool operator != (const Date& d) const
   {
       if( date[year] != d.date[year]  || date[month] != d.date[month] || date[day] != d.date[day] || date[hour] != d.date[hour]  || date[minute] != d.date[minute] || date[sec] != d.date[sec])
           return true;
       return false;
   }
    
};

int pow(int a,int b)
{
    int ans = 1;
    for( int i = 0 ; i < b ; i++ )
        ans *= a;
    
    return ans;
}

Date add_1h (Date d1)          //对较小的一个日期加1h
{
    d1.date[hour]++;                //h+1
    
    if( d1.date[hour] == 24 )       //如果小时为24
    {
        d1.date[hour] = 0;          //小时归零
        d1.date[day]++;             //天数+1
        if( d1.date[day] == 31 )        //如果天数为31
        {
            d1.date[day] = 1;       //天数归1
            d1.date[month]++;       //月+1
            if( d1.date[month] == 13 )      //月等于13
            {
                d1.date[month] = 1;         //月归1
                d1.date[year]++;            //年+1
            }
        }
    }
    
    return  d1;         //返回加1h后的答案
}

//vector<TASK> TA;       //存储所有助教对应的任务信息
int y = 0;
int ti[5];
//TASK now;
string s2;
Date Allstart,Allend,now;
vector<pair<Date,Date>> manage;
//vector<Date> manage;
vector<Date> TA;
//pair<Date, int> now;
map<Date,int> people;
map<Date,bool> vis;

//set<pair<Date,int>,cmp> TA;

void init()
{
    y = 0;
//    s1.clear();
    s2.clear();
    manage.clear();
//    for( int i = 0 ; i < sum ; i++ )
    TA.clear();
    people.clear();
    vis.clear();
}

void change(int t)
{
    if( t < 10 )      //不够两位数则补零
        cout<<0<<t;
    else
        cout<<t;
}


void output_start(int i)
{
    cout << "appointment possible from ";
    cout << setw(2) << setfill('0') << manage[i].first.date[month]<<'/';
    cout << setw(2) << setfill('0') << manage[i].first.date[day]<<'/';
    cout << setw(4) << setfill('0') << manage[i].first.date[year]<<' ';
    cout << setw(2) << setfill('0') << manage[i].first.date[hour]<< ':';
    cout << setw(2) << setfill('0') << manage[i].first.date[minute] << ':';
    cout << setw(2) << setfill('0') << manage[i].first.date[sec];
}

void output_end(int i)
{
    cout<<"to ";
    
    cout << setw(2) << setfill('0') << manage[i].second.date[month]<<'/';
    cout << setw(2) << setfill('0') << manage[i].second.date[day]<<'/';
    cout << setw(4) << setfill('0') << manage[i].second.date[year]<<' ';
    cout << setw(2) << setfill('0') << manage[i].second.date[hour]<< ':';
    cout << setw(2) << setfill('0') << manage[i].second.date[minute] << ':';
    cout << setw(2) << setfill('0') << manage[i].second.date[sec]<<endl;
}

int main()
{
//    ios::sync_with_stdio(false);
    
    int t = 0,num = 0,sum = 0;
    cin>>t;
    
    Allstart.date[year] = 1800;         //总开始
    Allstart.date[month] = 1;
    Allstart.date[day] = 1;
    for( int i = 3 ; i<= 5 ; i++ )
        Allstart.date[i] = 0;
    
    Allend = Allstart;                  //总结束
    Allend.date[year] = 2200;
    
    for( int l = 1 ; l <= t ; l++ )
    {
        
        init();
        
        cin>>num;       //助教人数
        
        for( int i = 0 ; i < num ; i++ )
        {
//            cout<<endl;
//            cout<<" ===== sum"<<endl;
            cin>>sum;          //当前助教任务数量
            
            for(int j = 0 ; j < sum ; j++ )        //初始化该助教所有任务
            {
//                cout<<" +++++++ "<<j<<endl;
                cin>>y;
                for( int k = 0 ; k < 5 ; k++ )
                    cin>>ti[k];
                
                now.initialize(y, ti);             //开始时间
//                now.second = 0;                 //表示当前记录时间为开始时间
                if( !vis[now] )          //当且仅当输入时间没有重复时,存储时间
                {
                    TA.push_back(now);
                    vis[now] = true;
                }
                
                people[now]++;
                
               
                
                cin>>y;
                for( int k = 0 ; k < 5 ; k++ )
                    cin>>ti[k];
                
                now.initialize(y, ti);               //结束时间
//                now.second = 1;                 //表示当前记录时间为开始时间
                if( !vis[now] )       //当且仅当输入时间没有重复时,存储时间
                {
                    vis[now] = true;
                    TA.push_back(now);
                }
                
                people[now]--;
                
                
                
                getline(cin,s2);        //去掉后面所有的字符串
                
//                now.ta_name = s1;                       //助教姓名及任务信息
//                now.task_name = s2;
                
                
            }
        }
        
        sort(TA.begin(), TA.end());        //所有任务时间排序
        
/*        for( int i = 0 ; i < TA.size() ; i++ )
        {
            output_start1(i);
//            output_end1(i);
        }
        cout<<" ========================== ta"<<endl;
        cout<<endl;*/
        
        if( TA.empty() )        //若没有任何安排,就是所有时间
        {
            pair<Date, Date> m;
            m.first = Allstart;
            m.second =  Allend;
            manage.push_back(m);
        }
        else
        {
            int leave = 0;  //接下来时段不可参加会议人数
            Date left = Allstart,right = Allstart;       //被选时间的开始时间
            bool judge = true;      //标志当前是否有left指针
            
            //遍历所有时间段
            for( int  i = 0 ; i < TA.size() ; i++ )
            {
                //left指针一定在离开人数减少到1或2个及以上到0个的时间
                if( (leave + people[TA[i]] == 1 || leave + people[TA[i]] == 0 )&& i < TA.size() - 1 && i > 0  && leave > 1  )
                {
                    //要么在最后一个时间点前
                    //要么最后一个时间点不等于总结束
                    //且保证人数合法
                    if( (leave + people[TA[i]] == 1 && num > 2 ) && (i < TA.size() - 1 || (i == TA.size() - 1 && TA[i] != Allend)) )
                    {
                        left = TA[i];
                        judge = true;
                    }
                }
                else if( (leave == 1 && people[TA[i]] > 0) || (leave == 0 && people[TA[i]] > 1) )
                {
//                    cout<<" !! "<<endl;
                    //right一定在离开人数从1增加到2及以上或从0增加到2及以上的时间
                    right = TA[i];
                    
                    //如果judge有效
                    //如果总人数只有两个,不合法,因为这个区段里至少有一个人离开
                    //如果间隔没有1小时,不合法
                    Date left1 = add_1h(left);
                    if( judge && num > 2 && (left1 < right || left1 == right))
                    {
                        //left到当前时间可选
                        pair<Date, Date> m;
                        m.first = left;
                        m.second = right;
                        manage.push_back(m);
                    }
                  
                    judge = false;
                }
                
                //如果到结尾存在有效left,则直接将结束时间设置为总结束
                if( i == TA.size() - 1 && judge )
                {
//                    cout<<" %% "<<endl;
                    //如果有一个人离开
                    //总人数如果没有两个,不合法
                    //时间间隔至少1h
                    //或者没人离开,且最后一个不等于总结束
                    Date left1 = add_1h(left);
                    if( leave + people[TA[i]] == 1 && num > 2 && (left1 < Allend || left1 == Allend))
                    {
                        //left到当前时间可选
                        pair<Date, Date> m;
                        m.first = left;
                        m.second = Allend;
                        manage.push_back(m);
                    }
                    else if( leave + people[TA[i]] == 0 && (left1 < Allend || left1 == Allend) && TA[i] != Allend )
                    {
                        //left到当前时间可选
                        pair<Date, Date> m;
                        m.first = left;
                        m.second = Allend;
                        manage.push_back(m);
                    }
                }
                         
                leave += people[TA[i]];     //得到将要离开的人数
                   
            }
        
        }

        cout<<"Scenario #"<<l<<":"<<endl;
        if( manage.empty() )
            cout<<"no appointment possible"<<endl;
        else
        {
       for( int i = 0 ; i < manage.size() ; i++ )
            {
                output_start(i);     //输出当前的起始时间
                output_end(i);
            }
        }
        
        cout<<endl;
        
    }
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天翊藉君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值