第五天—日期问题

日期问题是蓝桥杯、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;
}

关键技巧拆解

  1. 查表法:用数组存储每月天数,避免重复计算,高效又不易错;
  2. 星期推算:利用%7实现星期的循环(周六 (6)+1 = 周日 (0),完美闭环);
  3. 条件简化:日期 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 核心要点回顾

  1. 枚举逻辑:年→月→日三重循环是基础,时间枚举按需叠加;
  2. 闰年判断:记住完整规则,别漏 “不能被 100 整除” 或 “能被 400 整除”;
  3. 星期推算:周日设为 0,用%7循环最方便;
  4. 细节把控:数组下标、格式化输出、条件简化(如d%10==1)。

4.2 常见陷阱

  • 漏处理闰年 2 月 29 日;
  • 星期推算时编号规则混乱;
  • 数组下标越界(如用 day_in_dyas [0] 表示 1 月);
  • 格式化输出位数不足(如输出 2022-1-1 而非 2022-01-01);
  • 大范围枚举未优化,导致超时。

4.3 进阶方向

掌握基础后,可挑战更复杂的日期问题:

  1. 日期差值计算:求两个日期之间相差的天数;
  2. 特殊日期定位:判断某一天是当年的第 N 天;
  3. 节假日处理:结合实际节假日规则(如春节、国庆);
  4. 时区转换:处理跨时区的时间换算。

总结

​ 日期问题看似繁琐,实则套路化极强 —— 只需练熟通用框架,吃透闰年、星期、下标这几个核心点,就能在竞赛中快速解题。建议多刷几道蓝桥杯真题(如 “日期问题”“小明的生日”),熟能生巧。

posted @ 2025-12-23 22:11  smile_Lx  阅读(0)  评论(0)    收藏  举报