[noip2016-普及] 回文日期

GitHub跳转原题关系图返回列表

回文条件把月日钉死在年份的倒序上:枚举年份、还原月日并校验真实日期,落在区间内则计数。

OJ: roj

题目 ID: 3635

难度:普及-

标签:枚举回文日期与日历python

创建: 2026-10-02 11:56

更新: 2026-10-06 15:33

题目描述

用 8 位数字 YYYYMMDD 表示日期,若这 8 位顺读倒读相同则称回文日期。给定两个真实日期 d1≤d2d_1 \le d_2,求 [d1,d2][d_1,d_2] 内真实存在的回文日期个数。月份天数按公历规则,闰年为 4 的倍数且非 100 的倍数,或 400 的倍数。样例 1 输入 20110101 / 20111231,输出 1;样例 2 输入 20000101 / 20101231,输出 2。

思路

回文要求 MMDD 等于年份的倒序,因此年份一旦确定,月日就唯一确定。枚举起止年份之间至多 9000 个年份,倒序取出月日,先判月份是否在 1∼121\sim12,再按当月天数(2 月结合闰年)判日期是否存在,拼回 8 位日期落在区间内即计数。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 11:57
# update_at: 2026-10-02 12:04

import sys

MDAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)  # 平年每月天数


def solve() -> None:
    date1, date2 = map(int, sys.stdin.read().split())
    first_year, last_year = date1 // 10000, date2 // 10000  # 起止年份

    ans = 0
    for year in range(first_year, last_year + 1):
        # 关键观察:8 位回文日期的后四位 = 前四位年份的倒序,年份一旦选定,月日就被唯一确定
        mmdd = str(year)[::-1]
        month, day = int(mmdd[:2]), int(mmdd[2:])
        if not 1 <= month <= 12:
            continue                      # 倒序出的月份不在 1~12,该年没有回文日期
        days = MDAYS[month - 1]
        is_leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)  # 题面的闰年规则
        if month == 2 and is_leap:
            days += 1                      # 闰年 2 月 29 天
        if not 1 <= day <= days:
            continue                      # 该月没有这一天,构不成真实日期
        date = year * 10000 + month * 100 + day  # 拼回 8 位日期
        ans += date1 <= date <= date2      # 只有首尾年份可能越出区间

    print(ans)


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 15:32
 * update_at: 2026-10-06 15:32
 */
#include <cstdio>
using namespace std;

typedef long long ll;

int mdays[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 平年每月天数,下标即月份

// 闰年判定:4 的倍数且不是 100 的倍数,或 400 的倍数
bool is_leap(ll year) {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

int main() {
    ll date1, date2;   // 起止日期(8 位整数)
    ll first_year, last_year;
    ll year;
    ll ans = 0;

    scanf("%lld %lld", &date1, &date2);
    first_year = date1 / 10000; // 起始年份
    last_year = date2 / 10000;  // 终止年份

    for (year = first_year; year <= last_year; year++) {
        // 回文要求后 4 位 = 年份倒序,于是年份一确定,月日就唯一确定
        ll a = year / 1000;       // 年份千位
        ll b = year / 100 % 10;   // 年份百位
        ll c = year / 10 % 10;    // 年份十位
        ll d = year % 10;         // 年份个位
        ll month = d * 10 + c;    // 倒序后的前两位作月份
        ll day = b * 10 + a;      // 倒序后的后两位作日期

        if (month < 1 || month > 12) {
            continue; // 倒序出的月份不合法,该年没有回文日期
        }
        ll days = mdays[month];   // 本月天数
        if (month == 2 && is_leap(year)) {
            days = 29;
        }
        if (day < 1 || day > days) {
            continue; // 该月没有这一天
        }

        ll date = year * 10000 + month * 100 + day; // 拼回 8 位日期
        if (date1 <= date && date <= date2) {       // 只有首尾年份可能越出区间
            ans++;
        }
    }

    printf("%lld\n", ans);
    return 0;
}