日期计算属于蓝桥杯中的高频考点,以下几道算法能让大家轻松掌握日期计算
- 题型特点:通常要求根据给定的日期,计算相隔的天数、星期几的变化,或者根据特定条件推算出某个日期等,一定要注意好年份(不同年,具体的日期就不同)
- 对应知识点
- 历法知识:需要了解公历的基本规则,如闰年的判断规则(普通年份(2月29天)能被 4 整除但不能被 100 整除的为闰年(2月28天),世纪年份能被 400 整除的是闰年),每个月的天数等。平年有365天,闰年有366天。
- 数学运算:涉及到日期之间的差值计算,可能需要进行年、月、日的分别运算和调整,有时还需要考虑跨年度、跨月份的情况。
- 数据处理:可能会涉及到将输入的日期数据进行解析和处理,例如将字符串形式的日期转换为可以进行计算的数字形式。
学习途径:
1、跑步(蓝桥真题)
编辑
1、跑步
跑步
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
问题描述
小蓝每周六、周日都晨跑,每月的 11、1111、2121、3131 日也晨跑。其它时间不晨跑。
已知 20222022 年 11 月 11 日是周六,请问小蓝整个 20222022 年晨跑多少天?
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
运用到了知识点:
#include <iostream> using namespace std; int main() { // 首先判断是否为闰年,不是闰年,2月有29天 int day = 2; int flag = 0; // 本题主打合理运用数据进行计算 int monthDay[] = {31,29,31,30,31,30,31,31,30,31,30,31}; for(int i=0; i<sizeof(monthDay)/sizeof(monthDay[0]); ++i){ for(int j=0; j<monthDay[i]; ++j){ if(i==0&&(j==1||j==2)) continue; // 跳过重复计算 flag++; if(flag%6==0 || flag%7==0 || j+1==11 || j+1==1|| j+1==21 || j+1==31) day++; } } cout<<day<<endl; return 0; } // 数组定义的三种格式 // 数据类型 数组名[长度] // 数据类型 数组名[数组长度] = { 值1,值2 ... }; // 数据类型 数组名[] = { 值1,值2 ... };
在做跑步,这道题目时,我初步接触日期计算这种题型。
闰年、平年的运用,数组长度大小的计算 (复习+开拓)
2、跑步计划
问题描述
小蓝计划在某天的日期中出现 11 时跑 55 千米,否则只跑 11 千米。注意日期中出现 11 不仅指年月日也指星期。
请问按照小蓝的计划,20232023 年小蓝总共会跑步锻炼多少千米?例如,55 月 11 日、11 月 1313 日、1111 月 55 日、44 月 33 日 (星期一) 小蓝会跑 55 千米,而 55 月 2323 日小蓝会跑 11 千米 (示例日期均为 20232023 年)
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
#include <iostream> using namespace std; int main() { int s = 0; int monthDay[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // 每月日期 // 需考虑 3 种情况 // 1、月份 // 2、日期 // 3、星期 , 找准1月1,是几月几日非常重要 int flag = 0; for(int i=0; i<sizeof(monthDay)/sizeof(monthDay[0]); ++i){ for(int j=0; j<monthDay[i]; ++j){ flag++; if(i+1==1 || i+1>=10){ // 月份 s += 5; }else if(j+1==1 || (10<=j+1 && j+1<=19) || j+1 == 21 || j+1 == 31){ // 日 s += 5; }else if(flag%7==2){ // 星期 s += 5; }else{ s++; } } } cout<<s<<endl; return 0; }
在单纯的日期里面,开始涉及星期。
3、定时任务
问题描述
Cron 表达式在定时任务中经常被使用,在这里我们用了一种简化后的版本 SimpleCron 表达式:SimpleCron 表达式是一个具有时间含义的字符串,字符串以 44 个空格隔开,分为 55 个域,格式为 XXXXXXXXXX,其中 XX 是一个域的占位符。55 个域从左至右依次为秒 (0−59)(0−59)、分钟 (0−59)(0−59)、小时 (0−23)(0−23)、日期 (1−31)(1−31)、月份 (1−12)(1−12),其中括号内为他们各自的取值范围。同时域内取值也可以使用一些特殊字符(每个域内只能使用一种特殊字符):
- 特殊字符 ∗∗ (ASCII 码为 4242)表示所有可能的值。例如:在分钟域内表示每一分钟;在日期域内表示月内的每一天。
- 特殊字符 ,, (ASCII 码为 4444)表示列出枚举值。例如:在秒域内,3,203,20 表示分别在 33 秒和 2020 秒执行一次任务。
- 特殊字符 −− (ASCII 码为 4545)表示范围,可以视为连续的若干个枚举值。例如:1−51−5 等价于 1,2,3,4,51,2,3,4,5。
例如,421,3,151−31∗421,3,151−31∗表示的含义是每个月份中的每一天中的 01:02:0401:02:04、03:02:0403:02:04、15:02:0415:02:04 这三个时刻各执行一次,在 20232023 年一共会执行 10951095 次。
现在给出你一个合法的 SimpleCron 表达式,其中用到的所有数字均没有前导零。请问在 20232023 一整年当中,使用了这个表达式的定时任务总计会执行多少次?
输入格式
输入一行,包含一个 SimpleCron 字符串。
输出格式
输出一行,包含一个整数表示答案。
样例输入
4 2 1,3,15 1-31 *
样例输出
1095
评测用例规模与约定
对于所有评测用例,0≤0≤ 秒域的取值 ≤59≤59,0≤0≤ 分钟域的取值 ≤59≤59,0≤0≤ 小时域的取值 ≤23≤23,1≤1≤ 日期域的取值 ≤31≤31,1≤1≤ 月份域的取值 ≤12≤12。
// 本题我最开始的想法就是暴力破解,但是暴力破解本题,是何等的烧脑,因为用单纯的数字表示,是苍白无力的!
// 这是就体验到了,数据结构的强大,而本题就巧妙运用vector中的.size(),简单的代替了数字,虽然它只是入门级别的。
#include "bits/stdc++.h" using namespace std; // 我很佩服这道题,缘由是因为,它通过vector巧妙地代替了数字,合理的搭配、vector的巧妙运用与for-range的运用,让人感觉道赏心悦目 vector<int> generateNum(string str, int range){ // 生成数量 vector<int> res; // (每个域内只能使用一种特殊字符) int cur=0; int lis = -1; // 作用于-时, for(char c : str){ if(c==','){ res.push_back(cur); cur = 0; }else if(c=='-'){ lis = cur; cur = 0; }else if(c=='*'){ for(int i=0; i<range; i++) res.push_back(i+1); return res; }else{ // 这个用来计算时间 cur = cur*10 + c-'0'; } } // 存入 if(lis!=-1){ for(int i=lis; i<=min(range,cur); ++i) res.push_back(i); }else{ res.push_back(cur); } return res; } int main() { int monthDay[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // 每个月份,对应的天数 // 用集合代替,次数,简直是一个天才 string S,M,H,D,MON; cin>>S>>M>>H>>D>>MON; // 输入所有 // 计算秒、分、时的可能 int num1 = generateNum(S,60).size()*generateNum(M,60).size()*generateNum(H,24).size(); // 对月份也是有要求的 int num2 = 0; for(int i : generateNum(MON,12)){ // 每个i都是月份 num2+= generateNum(D,monthDay[i-1]).size()*num1; } cout<<num2<<endl; return 0; }
用vector代表了每个区间的数量/次数,这种替换,让人感到心旷神怡。
4、神奇闹钟
问题描述
小蓝发现了一个神奇的闹钟,从纪元时间(19701970 年 11 月 11 日 00:00:0000:00:00)开始,每经过 xx 分钟,这个闹钟便会触发一次闹铃 (纪元时间也会响铃)。这引起了小蓝的兴趣,他想要好好研究下这个闹钟。
对于给出的任意一个格式为 уууу-MM-ddHH:mm:ssуууу-MM-ddHH:mm:ss 的时间,小蓝想要知道在这个时间点之前 (包含这个时间点) 的最近的一次闹铃时间是哪个时间?
注意,你不必考虑时区问题。
输入格式
输入的第一行包含一个整数 TT,表示每次输入包含 TT 组数据。
接下来依次描述 TT 组数据。
每组数据一行,包含一个时间(格式为 уууу-MM-ddHH:mm:ssуууу-MM-ddHH:mm:ss)和一个整数 xx,其中 xx 表示闹铃时间间隔(单位为分钟)。
输出格式
输出 TT 行,每行包含一个时间(格式为 уууу-MM-ddHH:mm:ssуууу-MM-ddHH:mm:ss),依次表示每组数据的答案。
样例输入
2 2016-09-07 18:24:33 10 2037-01-05 01:40:43 30
样例输出
2016-09-07 18:20:00 2037-01-05 01:30:00
评测用例规模与约定
对于所有评测用例,1≤T≤10,1≤x≤10001≤T≤10,1≤x≤1000,保证所有的时间格式都是合法的。
#include "bits/stdc++.h" #define ll long long using namespace std; ll generate(int y,int M,int d,int h,int m,int s,int sub){ // 这里面有一个精致的细节,时间是从1月1日开始,而非0点0时0分! int D = 0; // 计算天数 for(int i=1970; i<=y-1; ++i){ // 算年 if( (i%4==0 && i%100!=0) || i%400==0){ // 是闰年,!!!这个判断相当重要 D+=366; }else{ // 是平年 D+=365; } } for(int i=1; i<=M-1; ++i){ // 算月!!!是到 M-1月,否则会多运算 if(i==1 || i==3 || i==5 || i==7 || i==8 || i==10 || i==12) D+=31; else if(i==2&&((y%4==0 && y%100!=0) || y%400==0)) D+=29; else if(i==2) D+=28; else D+=30; } D+=(d-1); // 算日 // 返回分钟 ll num = D*(24*60) + h*60 + m; return num; } // 从新来过 // 这次从新来过 int main(){ int t; cin>>t; while(t--){ // 获得时间差 int y,M,d,h,m,s,sub; scanf("%d-%d-%d %d:%d:%d %d",&y,&M,&d,&h,&m,&s,&sub); // 消减时间差 ll all_min = generate(y,M,d,h,m,s,sub); // 生成总时间 ll acc_time = all_min - (all_min%sub); // 现在已经取到时间,仅需顺流而上即可 ll lday,lh,lm; lday = acc_time/(24*60); // 求出天数 lh = acc_time/(60)%24; // 求出小时 lm = acc_time%(60);// 求出分钟 ll YYYY = 1970; ll MM = 1; ll dd = 1; ll HH = lh; ll mm = lm; ll ss = 0; while(lday>=0){ // 加年 if( (YYYY%4==0 && YYYY%100!=0) || YYYY%400==0){ if(lday<366) break; lday-=366; }else{ if(lday<365) break; lday-=365; } YYYY++; } while(lday>0){ // 加月 if(MM==1 || MM==3 || MM==5 || MM==7 || MM==8 || MM==10 || MM==12){ // 31天的月 if(lday<31) break; lday-=31; }else if(MM==2 &&((YYYY%4==0 && YYYY%100!=0) || YYYY%400==0)){ // 闰年 if(lday<29) break; lday-=29; }else if(MM==2){ if(lday<28) break; lday-=28; }else{ if(lday<30) break; lday-=30; } MM++; // 说实话,我对这一步,表示有点无法理解 } dd += lday; printf("%04lld-%02lld-%02lld %02lld:%02lld:%02lld\n",YYYY,MM,dd,HH,mm,ss); // 切记,这里一定要换行 } return 0; } // 记录,如何判断,闰年中的闰月 // 劝告,最好封装一下函数,因为越多的代码,就意味着可能有更多的纰漏 // 不过咱们那个时区计算,好像可有可无
其实本题,这样写,不是我心中,最好的解法,因为模拟时,需要用到有些重复代码很多次,固可分割组装成函数,以下是封装后的代码。
bool isLeapYear(int year){ return ((year%4==0 && year%100 !=0) || year%400 == 0); } int getDaysInMonth(int year, int month){ if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) return 31; if(month==2) return isLeapYear(year) ? 29 : 28; return 30; }
5、扫把扶不扶【算法赛】
问题描述
你正在参加一场程序员的终极面试,和你竞争的是小蓝。你们都顺利地通过了前几轮筛选,来到了最后一轮的现场面试环节。
你到达公司门口的时间是 S1S1,和面试官约定的面试时间是 S2S2。在约定时间到达前(S1∼S2S1∼S2),你可以选择提前开始面试。然而,如果超过了面试时间,即便只晚了 11 秒,你都将失去面试机会,直接被淘汰。
你的面试会持续 TT 分钟。
小蓝到达公司门口的时间是 S3S3,和面试官约定的面试时间是 S4S4。由于你在前几轮的表现比他好,因此小蓝必须在你结束面试后才能开始他的面试。如果小蓝在他的面试时间之前没有开始面试,他也将失去面试机会,直接被淘汰。
面试存在竞争机制:如果只有你或小蓝中的一人参与了面试,那么参与面试的那个人将胜出。如果你和小蓝都没有参与面试,你们将双双失败。
“那如果两个人都参与了面试,谁将胜出呢?”你正想着,突然发现公司门口的不远处躺着一把扫把,看样子是面试官故意放的。你想扶起它,但这需要你花费 XX 分钟的时间。 根据你所了解的套路,如果你扶起了扫把,并参加了面试,那么你和小蓝的竞争中,你必定能够胜出。相对的,如果你没有扶起扫把,并且你和小蓝都参加了面试,那么小蓝必定能够胜出。
现在,你和小蓝都会采取最优的策略来确保自己胜出(如果无论如何也无法使自己胜出,则应优先确保双双失败)。请问最后的结果会是如何?
输入格式
输入包含多组数据。
第一行包含一个整数 NN(1≤N≤1031≤N≤103),表示数据的组数。
接下来 NN 组数据,每组数据包含三行:
- 第一行包含两个时间字符串 S1S1, S2S2,以空格分隔,表示你到达公司门口的时间和你和面试官约定的面试时间。
- 第二行包含两个时间字符串 S3S3, S4S4,以空格分隔,表示小蓝到达公司门口的时间和小蓝和面试官约定的面试时间。
- 第三行包含两个整数 T,xT,x(1≤T,x≤1201≤T,x≤120),以空格分隔,分别表示你的面试时长和你扶起扫把所需的时间(单位:分钟)。
时间的格式为 HH:MM:SSHH:MM:SS,其中 HHHH 表示小时(00≤HH≤2100≤HH≤21),MMMM 表示分钟(00≤MM≤5900≤MM≤59),SSSS 表示秒(00≤SS≤5900≤SS≤59)。
输出格式
对于每组数据,输出一个字符串,表示最终的结果。
- 如果你胜出了,输出
You。- 如果小蓝胜出了,输出
Lan。- 如果你们双双失败了,输出
Draw。
#include <iostream> using namespace std; int main() { int t; cin>>t; while(t--){ int yh1,ym1,ys1,yh2,ym2,ys2; int lh1,lm1,ls1,lh2,lm2,ls2; int T,x; scanf("%d:%d:%d %d:%d:%d",&yh1,&ym1,&ys1,&yh2,&ym2,&ys2); scanf("%d:%d:%d %d:%d:%d",&lh1,&lm1,&ls1,&lh2,&lm2,&ls2); scanf("%d %d",&T,&x); // 将四个值转化为时间,一秒为基础单位 int in1,start1; int in2,start2; in1 = yh1*60*60 + ym1*60 + ys1; start1 = yh2*60*60 + ym2*60 + ys2; in2 = lh1*60*60 + lm1*60 + ls1; start2 = lh2*60*60 + lm2*60 + ls2; T = T*60; x = x*60; // You局 // 扶起了扫把 // 面试结束时间超过小蓝面试时间 // 小蓝迟到了,You没迟到 if(start1>=in1){ if(start1-in1 >= x || start1+T > start2 || in2 > start2){ cout<<"You"<<endl; continue; } } // Draw局 // 你俩都迟到了 if(start1<in1 && start2<in2){ cout<<"Draw"<<endl; continue; } // Lan局 // 你迟到了 // 小蓝参与了 if(start2>=in2){ cout<<"Lan"<<endl; continue; } } return 0; }
这是一道模拟+日期计算题,从scanf(); 的格式输入,到对闰年平年的判断。
如:MM==2 &&((YYYY%4==0 && YYYY%100!=0) || YYYY%400==0)
到最后对函数的封装,三元运算符的应用。简直完美。
简单的题,不一定简单。
笔者感悟:
做题时,不要害怕自己的想法不是最优解,因为答案也不一定是最优解,而是应该先自己做一遍,大概15min作用,无思路,在借鉴高质量答案,将自己与它的不同标志。长期这样可培养独立思考能力。