利用回文日期的结构直接由年份反推月份和日期,再判断是否为真实日期并检查是否落在区间内。
OJ: luogu
题目 ID: P2010
难度:普及-
标签:枚举模拟
日期: 2026-06-19 00:55
题意
给定两个 8 位日期 date1 和 date2,要求统计闭区间 [date1, date2] 中有多少个真实存在的回文日期。
这里的“真实存在”指:
- 月份必须在
1..12 - 日期必须在当月的合法范围内
2月还要区分平年和闰年
思路
先看一个最直接的朴素做法:
从 date1 开始一天一天往后推到 date2,每遇到一个真实日期,就判断它的 8 位表示是否回文。
这个做法很好理解,也方便拿来对拍:
cpp
// brute.cpp:按天递增枚举区间里的每一个真实日期,直接判断是否回文。
#include <bits/stdc++.h>
using namespace std;
string left_date, right_date;
bool is_leap(int year) {
if (year % 400 == 0) {
return true;
}
if (year % 100 == 0) {
return false;
}
return year % 4 == 0;
}
int days_in_month(int year, int month) {
if (month == 1 || month == 3 || month == 5 || month == 7 ||
month == 8 || month == 10 || month == 12) {
return 31;
}
if (month == 4 || month == 6 || month == 9 || month == 11) {
return 30;
}
if (month == 2) {
if (is_leap(year)) {
return 29;
}
return 28;
}
return 0;
}
void split_date(const string &s, int &year, int &month, int &day) {
year = stoi(s.substr(0, 4));
month = stoi(s.substr(4, 2));
day = stoi(s.substr(6, 2));
}
string format_date(int year, int month, int day) {
char buf[16];
snprintf(buf, sizeof(buf), "%04d%02d%02d", year, month, day);
return string(buf);
}
bool is_palindrome(const string &s) {
for (int i = 0; i < 4; i++) {
if (s[i] != s[7 - i]) {
return false;
}
}
return true;
}
void next_day(int &year, int &month, int &day) {
day++;
if (day <= days_in_month(year, month)) {
return;
}
day = 1;
month++;
if (month <= 12) {
return;
}
month = 1;
year++;
}
void solve() {
int year, month, day;
int ans = 0;
split_date(left_date, year, month, day);
while (true) {
string cur = format_date(year, month, day);
if (is_palindrome(cur)) {
ans++;
}
if (cur == right_date) {
break;
}
next_day(year, month, day);
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> left_date >> right_date;
solve();
return 0;
}但这题还有一个更关键的结构性质:
如果一个日期写成
yyyy mm dd
并且整个 8 位串是回文,那么它一定长成:
abcd dc ba
也就是说:
- 年份确定后,月份就被唯一确定为
dc - 日期也被唯一确定为
ba
所以我们没必要在区间里把每一天都试一遍,只要枚举区间涉及到的每一个年份:
- 用年份反推出对应的
mm和dd - 判断这个年月日是不是真实日期
- 再判断这个回文日期是否落在给定区间内
这样每年最多检查一次,复杂度就很小了。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
string left_date, right_date;
bool is_leap(int year) {
if (year % 400 == 0) {
return true;
}
if (year % 100 == 0) {
return false;
}
return year % 4 == 0;
}
int days_in_month(int year, int month) {
if (month == 1 || month == 3 || month == 5 || month == 7 ||
month == 8 || month == 10 || month == 12) {
return 31;
}
if (month == 4 || month == 6 || month == 9 || month == 11) {
return 30;
}
if (month == 2) {
if (is_leap(year)) {
return 29;
}
return 28;
}
return 0;
}
bool is_valid_date(int year, int month, int day) {
if (month < 1 || month > 12) {
return false;
}
if (day < 1 || day > days_in_month(year, month)) {
return false;
}
return true;
}
string format_date(int year, int month, int day) {
char buf[16];
snprintf(buf, sizeof(buf), "%04d%02d%02d", year, month, day);
return string(buf);
}
void solve() {
int start_year = stoi(left_date.substr(0, 4));
int end_year = stoi(right_date.substr(0, 4));
int ans = 0;
for (int year = start_year; year <= end_year; year++) {
// 回文日期一定形如 abcd dc ba。
int month = (year % 10) * 10 + (year / 10) % 10;
int day = (year / 100 % 10) * 10 + year / 1000;
if (!is_valid_date(year, month, day)) {
continue;
}
string cur = format_date(year, month, day);
if (cur >= left_date && cur <= right_date) {
ans++;
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> left_date >> right_date;
solve();
return 0;
}复杂度
设左右端点的年份分别是 Y1 和 Y2。
时间复杂度是
总结
这题的关键不是日期模拟本身,而是先看出“回文日期由年份唯一决定后四位”这个结构。
先利用结构把搜索空间大幅缩小,再做合法性判断,会比按天枚举更自然。