第五天—日期问题
日期问题是蓝桥杯、CCF-CSP 等算法竞赛的 “常客”,几乎每年都会出现在省赛和国赛中。这类题目看似繁琐,但只要掌握核心的枚举思路和避坑技巧,就能拿分。
一、日期与时间的枚举方式
日期枚举的核心逻辑:从起始日期开始,逐天推进,直到结束日期,本质是 “年→月→日” 的三重循环;时间枚举则是 “时→分→秒” 的三重循环,可按需组合。
1.1 基本枚举思路
(1)日期三重循环(核心)
这是处理日期问题最通用的框架,覆盖 90% 以上的日期题:
for (int year = start_year; year <= end_year; year++) {
for (int month = 1; month <= 12; month++) {
for (int day = 1; day <= 当月天数; day++) {
// 处理当天的业务逻辑(如判断是否符合条件、统计数量等)
}
}
}
(2)时间三重循环
若题目涉及时分秒(如时间戳、倒计时问题),可用此框架:
for (int h = 0; h < 24; ++h) {
for (int m = 0; m < 60; ++m) {
for (int s = 0; s < 60; ++s) {
// 处理 h:m:s 对应的逻辑
}
}
}
(3)日期 + 时间组合枚举
复杂时间题(如计算两个时间点的差值)需结合日期和时间:
for (int year = start_year; year <= end_year; year++) {
for (int month = 1; month <= 12; month++) {
for (int day = 1; day <= 当月天数; day++) {
for (int h = 0; h < 24; ++h) {
for (int m = 0; m < 60; ++m) {
for (int s = 0; s < 60; ++s) {
// 处理 yyyy-mm-dd h:m:s 的逻辑
}
}
}
}
}
}
1.2 必背:时间单位换算
处理时间差值、跨单位计算时,这些换算关系是基础:
| 单位 | 换算关系 |
|---|---|
| 分钟 | 1 分钟 = 60 秒 |
| 小时 | 1 小时 = 60 分钟 = 3600 秒 |
| 天 | 1 天 = 24 小时 = 1440 分钟 = 86400 秒 |
| 周 | 1 周 = 7 天 |
| 年 | 平年 365 天,闰年 366 天 |
二、通用日期枚举框架(真题框架)
2.1 标准日期枚举框架
先掌握通用模板,后续所有日期题都可基于此修改:
#include <iostream>
using namespace std;
// 每个月的天数
int day_in_month[] = {0, 31, 28, 31,30,31,30,31,31,30,31,30,31};
// 判断是否为闰年
bool is_leap_year(int year){
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
// 获取某月的天数
int get_days_in_month(int year, int month){
if (month == 2 && is_leap_year(year)){
return 29;
}
return days_in_month[month];
}
int main(){
int start_year = 2022, end_year = 2022;
int week = 6; // 起始日期是星期几(0=周日,1=周一,...,6=周六)
int answer = 0;
for (int year = start_year; year <= end_year; year++){
for (int month = 1; month <= 12; month++){
int days = get_days_in_month(year, month);
for (int day = 1; day <= days; day++){
// 在这里添加题目的判断逻辑
week = (week + 1) % 7; // 推进到下一天
// 日期为 yyyy:mm:dd 星期 week
}
}
}
cout<<answer<<endl;
return 0;
}
2.2 真题实战:「跑步」省赛真题解析
题目要求
统计 2022 年晨跑天数,满足以下任一条件即晨跑:
- 当天是周六 / 周日;
- 当天是每月 1 日、11 日、21 日、31 日。
已知 2022 年 1 月 1 日是周六(对应 week=6)。
#include <iostream>
using namespace std;
int day_in_month[] = {0, 31, 28, 31, 30, 31, 30,31,31,30,31,30,31};
int main(){
int start_year = 2022;
int end_year = 2022;
int week = 6;
int answer = 0;
for (int year = start_year; year <= end_year; year++){
for (int month = 1; month <= 12; month++){
int days = day_in_month[month];
if (year % 4 == 0 && year % 100 != 0 && month == 2){
month = 29;
}else if(year % 400 == 0 && month == 2){
month = 29;
}
for (int day = 1; day <= days; day++){
if (week == 0 || week == 6 || day % 10 == 1){
answer++;
}
week = (week + 1) % 7;
}
}
}
cout<<answer<<endl;
return 0;
}
关键技巧拆解
- 查表法:用数组存储每月天数,避免重复计算,高效又不易错;
- 星期推算:利用%7实现星期的循环(周六 (6)+1 = 周日 (0),完美闭环);
- 条件简化:日期 1/11/21/31 的共性是 “个位为 1”,用d%10==1替代多次判断,简化代码。
三、避坑指南:这些细节千万别错!
3.1 闰年判断(高频陷阱)
正确规则:能被4整除但不能被100整除或 能被400整除`;
常见错误:只判断 “能被 4 整除”(如 1900 年是平年,却会被误判为闰年)。
3.2 月份天数记忆(笨方法但有效)
简单的数手指头就能搞定:
- 1/3/5/7/8/10/12 月:31 天(大月);
- 4/6/9/11 月:30 天(小月);
- 2 月:平年 28 天,闰年 29 天。
3.3 星期推算的两种方式
| 方式 | 编号规则 | 推进公式 | |
|---|---|---|---|
| 方式 1(推荐) | 0 = 周日,1 = 周一…6 = 周六 | week = (week + 1) %7 | |
| 方式 2 | 1 = 周一,2 = 周二…7 = 周日 | week = week%7 +1 |
推荐理由:因为取模运算更加自然
3.4 数组下标问题
存储月份天数的数组,下标 0 必须占位,让下标 1 对应 1 月、下标 2 对应 2 月…… 避免 “1 月对应下标 0” 的错位错误。
3.5 日期格式化输出
题目常要求特定格式(如 YYYY-MM-DD),用格式化输出符即可:
// 输出格式:2022-01-01(不足位数补0)
printf("%04d-%02d-%02d\n", year, month, day);
// %04d:4位数字,不足补0;%02d:2位数字,不足补0
// 输出格式:2022年01月01日
printf("%04d年%02d月%02d日\n", year, month, day);
3.6 效率优化(大范围枚举时用)
若需枚举 1000 + 年的日期,提前预处理能大幅提升效率:
// 技巧1:预处理闰年数组
bool leap[10005]; // 存储1~10000年是否为闰年
for (int i = 1; i <= 10000; i++) {
leap[i] = (i%4==0&&i%100!=0) || (i%400==0);
}
// 技巧2:预处理每年总天数
int days_in_year[10005];
for (int i = 1; i <= 10000; i++) {
days_in_year[i] = leap[i] ? 366 : 365;
}
四、总结与进阶
4.1 核心要点回顾
- 枚举逻辑:年→月→日三重循环是基础,时间枚举按需叠加;
- 闰年判断:记住完整规则,别漏 “不能被 100 整除” 或 “能被 400 整除”;
- 星期推算:周日设为 0,用%7循环最方便;
- 细节把控:数组下标、格式化输出、条件简化(如d%10==1)。
4.2 常见陷阱
- 漏处理闰年 2 月 29 日;
- 星期推算时编号规则混乱;
- 数组下标越界(如用 day_in_dyas [0] 表示 1 月);
- 格式化输出位数不足(如输出 2022-1-1 而非 2022-01-01);
- 大范围枚举未优化,导致超时。
4.3 进阶方向
掌握基础后,可挑战更复杂的日期问题:
- 日期差值计算:求两个日期之间相差的天数;
- 特殊日期定位:判断某一天是当年的第 N 天;
- 节假日处理:结合实际节假日规则(如春节、国庆);
- 时区转换:处理跨时区的时间换算。
总结
日期问题看似繁琐,实则套路化极强 —— 只需练熟通用框架,吃透闰年、星期、下标这几个核心点,就能在竞赛中快速解题。建议多刷几道蓝桥杯真题(如 “日期问题”“小明的生日”),熟能生巧。

浙公网安备 33010602011771号