2 条题解

  • 0
    @ 2025-11-28 10:38:39

    你好!这是一份经过优化的、标准的 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;
    }
    

    代码解析

    1. 构造法:我们没有去遍历每一天,而是只遍历了年份(i)。通过 year 反转得到 monthday,强行拼凑出一个回文数。例如 i=2016,我们拼凑出 20166102
    2. 合法性检查:拼凑出来的数字很可能是假日期(比如 20166102,61月02日显然不存在)。所以必须用 check_date 函数过滤掉无效日期。
    3. 范围过滤:由于我们是按年份循环,第一年构造出的回文日期可能早于起始日期 date1(比如要求 2011020220111231,但 2011 年构造出的是 20111102,在范围内;如果构造出的是 20110102,就不在范围内了),所以 palindrome_date < date1 || palindrome_date > date2 的判断必不可少。
    • 0
      @ 2025-11-28 10:37:48

      你好!我是你的 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 日)。这是一个合法日期。

      具体步骤:

      1. 范围确定:只遍历年份。提取 date1 的年份(前4位)和 date2 的年份(前4位),在这个范围内循环 i
      2. 构造回文
        • 对于每一个年份 i(比如 2010),把它的数字反转得到 rev_i(比如 0102)。
        • 拼接成完整的 8 位数日期:full_date = i * 10000 + rev_i
      3. 合法性判断
        • 先判断范围:full_date 必须在 date1date2 之间。
        • 再判断日期有效性:检查这个日期的月份日子是否真实存在。
          • 月份必须 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 位,算出 monthday
      • 判断 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
      上传者