2 条题解
-
0
你好!这是一份经过优化的、标准的 AC 代码,采用了**“枚举年份构造回文”**的思路。
这种方法比“一天天模拟”更简单、更不容易写错,且运行效率极高(只循环不到 9000 次),是考场上的最佳写法。
标准 AC 代码 (C++)
#include <iostream> using namespace std; // 定义每个月的天数,days[0] 留空,方便下标 1-12 对应月份 int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 判断是否为闰年 bool is_leap(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } // 检查一个 8 位数字是否是合法的日期 bool check_date(int date) { int year = date / 10000; int month = (date % 10000) / 100; int day = date % 100; // 1. 检查月份是否合法 if (month < 1 || month > 12) return false; // 2. 处理闰年的 2 月天数 // 注意:不要直接修改全局数组 days,否则会影响后续判断 int limit = days[month]; if (month == 2 && is_leap(year)) { limit = 29; } // 3. 检查日期是否合法 if (day < 1 || day > limit) return false; return true; } int main() { int date1, date2; cin >> date1 >> date2; int ans = 0; // 只需要遍历年份(前 4 位) // 从 date1 的年份 遍历到 date2 的年份 for (int i = date1 / 10000; i <= date2 / 10000; i++) { // --- 核心逻辑:构造回文日期 --- int year = i; int reverse_year = 0; // 这一部分将作为后 4 位(月日) int temp = year; // 简单的整数反转逻辑 while (temp > 0) { reverse_year = reverse_year * 10 + temp % 10; temp /= 10; } // 拼接成完整的 8 位日期 int palindrome_date = year * 10000 + reverse_year; // --- 检查逻辑 --- // 1. 必须在题目给定的范围内 [date1, date2] // (因为我们是按年份循环构造的,构造出的日期有可能小于 date1 或大于 date2) if (palindrome_date < date1 || palindrome_date > date2) continue; // 2. 必须是一个真实存在的日期 (比如不能是 20166102) if (check_date(palindrome_date)) { ans++; } } cout << ans << endl; return 0; }代码解析
- 构造法:我们没有去遍历每一天,而是只遍历了年份(
i)。通过year反转得到month和day,强行拼凑出一个回文数。例如i=2016,我们拼凑出20166102。 - 合法性检查:拼凑出来的数字很可能是假日期(比如
20166102,61月02日显然不存在)。所以必须用check_date函数过滤掉无效日期。 - 范围过滤:由于我们是按年份循环,第一年构造出的回文日期可能早于起始日期
date1(比如要求20110202到20111231,但2011年构造出的是20111102,在范围内;如果构造出的是20110102,就不在范围内了),所以palindrome_date < date1 || palindrome_date > date2的判断必不可少。
- 构造法:我们没有去遍历每一天,而是只遍历了年份(
-
0
你好!我是你的 OI 教练。
这道题(NOIP 2016 普及组 T2)是一道非常经典的日期模拟题目。虽然题目不难,但如果思路选错了,写起来会非常痛苦,而且容易出错。
我给你提供两种思路,并重点推荐第二种“聪明”的办法。
思路一:暴力枚举每一天(不推荐,写起来累)
最直观的想法是:从
date1开始,一天一天地往后加(调用NextDay函数),直到date2。每走一天,就判断一下这个 8 位数是不是回文。- 缺点:你需要写一个复杂的“下一天”函数,处理大月、小月、平年、闰年、跨年等逻辑,代码量大,容易写出 Bug。
- 复杂度:虽然年份跨度最大是 1000 年到 9999 年(约 300 多万天),计算机也能在 1 秒内跑完,但性价比不高。
思路二:枚举回文年份(强烈推荐,代码短)
这是一个“逆向思维”。 与其在几百万天里找回文日期,不如直接构造回文日期。
核心观察: 一个 8 位回文日期
ABCDDCBA,它的后 4 位(月、日)完全是由前 4 位(年)决定的! 例如:- 年份是
2016,那么为了回文,日期必须是6102(即 61 月 02 日)。很显然这不是个合法日期。 - 年份是
2010,那么为了回文,日期必须是0102(即 01 月 02 日)。这是一个合法日期。
具体步骤:
- 范围确定:只遍历年份。提取
date1的年份(前4位)和date2的年份(前4位),在这个范围内循环i。 - 构造回文:
- 对于每一个年份
i(比如 2010),把它的数字反转得到rev_i(比如 0102)。 - 拼接成完整的 8 位数日期:
full_date = i * 10000 + rev_i。
- 对于每一个年份
- 合法性判断:
- 先判断范围:
full_date必须在date1和date2之间。 - 再判断日期有效性:检查这个日期的月份和日子是否真实存在。
- 月份必须
1~12。 - 日子必须符合当月的天数(注意闰年 2 月有 29 天)。
- 月份必须
- 先判断范围:
3. 实现细节提示
如何反转年份得到后四位?
你可以用数学方法(取模
%和 除法/):int year = i; int month_day = 0; // 简单的反转整数逻辑 int temp = year; while(temp > 0){ month_day = month_day * 10 + (temp % 10); temp /= 10; } // full_date = year * 10000 + month_day;如何判断日期合法?
写一个
check函数。- 提前把每个月的天数存进数组:
days[13] = {0, 31, 28, 31, 30, ...}。 - 如果是闰年,把 2 月天数改成 29。
- 闰年判定:
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
- 闰年判定:
- 提取出
full_date的后 4 位,算出month和day。 - 判断
month > 12或者day == 0或者day > days[month]均为不合法。
4. 代码结构框架
#include <iostream> using namespace std; // 判断闰年 bool isLeap(int year) { // ... } // 检查日期是否真实存在 bool check(int date) { int year = date / 10000; int month = (date % 10000) / 100; int day = date % 100; // 1. 基础判断 month 1-12 // 2. 根据闰年设定2月天数 // 3. 判断 day 是否在合理范围内 } int main() { int date1, date2; cin >> date1 >> date2; int ans = 0; // 只需要循环年份,从 date1 的年份 到 date2 的年份 for (int i = date1 / 10000; i <= date2 / 10000; i++) { // 1. 构造回文日期 full_date // ... // 2. 判断 full_date 是否在 [date1, date2] 区间内 // 注意:因为只循环了年份,构造出的日期可能小于 date1 或 大于 date2 if (full_date < date1 || full_date > date2) continue; // 3. 判断日期是否合法 if (check(full_date)) { ans++; } } cout << ans << endl; return 0; }用这种“构造”的方法,循环次数不到 10000 次,速度飞快,代码逻辑也很清晰。加油!
- 1
信息
- ID
- 5749
- 时间
- 1000ms
- 内存
- 32MiB
- 难度
- 3
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者