1.2 回文日期(★)
题目信息
2020 年省赛 - 填空题
● C/C++ A组第7题;C/C++ B组第7题
● Java A组第7题
【问题描述】
2020 年春节期间,有一个特殊的日期引起了大家的注意:2020 年 2 月 2 日。因为如果将这个日期按 "yyyymmdd" 的格式写成一个 8 位数是 20200202,恰好是一个回文数。我们称这样的日期是回文日期。
有人表示 20200202 是 "千年一遇" 的特殊日子。对此小明很不认同,因为不到 2 年之后就是下一个回文日期:20211202 即 2021 年 12 月 2 日。
也有人表示 20200202 并不仅仅是一个回文日期,还是一个 ABABBABA 型的回文日期。对此小明也不认同,因为大约 100 年后就能遇到下一个 ABABBABA 型的回文日期:21211212 即 2121 年 12 月 12 日。算不上 "千年一遇",顶多算 "千年两遇"。
给定一个 8 位数的日期,请你计算该日期之后下一个回文日期和下一个 ABABBABA 型的回文日期各是哪一天。
【输入格式】
输入包含一个八位整数 ,表示日期。
对于所有评测用例, ,保证 是一个合法日期的8位数表示。
【输出格式】
输出两行,每行 1 个八位数。第一行表示下一个回文日期,第二行表示下一个 ABABBABA 型的回文日期。
【样例输入】
20200202
【样例输出】
20211202
21211212
【答案提交】
这是一道结果填空题,你只需要算出结果后提交即可。本题的结果作为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
懒人速读
给定(输入)一个 位数的日期,请找出(输出)该日期之后的下一个回文日期以及下一个 ABABBABA 型回文日期。
若给定的日期为 20200202,则会有下列结果。
● 下一个回文日期为20211202。
● 下一个 ABABBABA 型回文日期为21211212。
题目分析
核心考点
● (日期)枚举
● 合法日期判断
根据题意,我们可以将题目拆解为如何判断回文和如何枚举日期两个部分来解决。
1.如何判断回文
对于一个8位数的整型日期,可以通过除法和取余运算来获取它的每一位数字。
举例说明
若整数 date 的值为 20050511,它从高到低的每一数位上的数可以通过下列方法得到。
● data / 10000000 = 2;
● data / 1000000 % 10 = 0;
● data / 100000 % 10 = 0;
● data / 10000 % 10 = 5;
● data / 1000 % 10 = 0;
● data / 100 % 10 = 5;
● data / 10 % 10 = 1;
● data % 10 = 1。
对于本题,若用 、 、 、 、 、 、 、 分别存储每一位,则一个回文日期须满足:
一个 ABABBABA 型回文日期须满足以下条件。
对于一个日期字符串,可以直接通过下标来获取它的每一位数字。例如, string date ="20050511",它从高到低的每一位分别可以通过 data[0], data[1], , data[7] 得到。判断回文日期及ABABBABA 型回文日期的方式和上述方法类似。
不难看出,字符串获取日期的每一位数字是要比整型简单的,所以在判断回文日期及ABABBABA 型回文日期时可以将整型转换为字符串来操作,如参考代码1-2-1所示。
参考代码1-2-1
1 int date = 20050511;
2 string s = to_string(date); 将整型转换为字符串, s = "20050511"
3
4 // 判断回文日期
5 bool check1(int date){
6 string s = to_string(date);
7 if(s[0] == s[7] && s[1] == s[6] && s[2] == s[5] && s[3] == s[4]) return true;
8 return false;
9 }
10 // 判断 ABABBABA 型回文日期
11 bool check2(int date){
12 string s = to_string(date);
13 if(s[0] == s[2] && s[0] == s[5] && s[0] == s[7] && s[1] == s[3] && s[1] == s[4] && s[1] == s[6]) return true;
14 return false;
15 }
2.如何枚举日期
对于一个整型日期,可以用最简单的方式枚举,即令该日期不断 +1,直到出现满足 ABABBABA 型的回文日期。
但这样存在一个问题:会枚举出许多不合法的日期(如 20209999)。为此,我们需要对日期进行合法性判断:设 分别表示年、月、日, 表示第 个月的天数,那么当 或 或 或 时日期不合法,反之日期合法,如参考代码1-2-2所示。
参考代码1-2-2
1 int month[13] = {-1 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31};
2 bool check(int date){
3 string s = to_string(date);
4 // stoi -> 将字符串转为整型
5 int y = stoi(s.substr(0, 4)), m = stoi(s.substr(4, 2)), d = stoi(s.substr(6, 2));
6 if(y % 400 == 0 || (y % 4 == 0 && y % 100 != 0)) month[2] = 29;
7 else month[2] = 28;
8 if(m < 1 || m > 12 || d < 1 || d > month[m]) return false;
9 return true;
10 }
11 for(int i = date + 1 ; ; i ++){
12 if(!check(i)) continue ;
13 ...
14 }
不过这样的枚举量是相当大的,要在比赛中完成本题要求的所有测试数据不太现实。
那么,还有什么枚举方法呢?
可以直接枚举合法日期。枚举合法日期只需模拟日期的变化规律即可,对应参考代码如下所示。
参考代码1-2-3
1 int month[13] = {-1 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31};
2 ...
3 int date = 20050511;
4 int y = date / 10000 , m = date / 100 % 100 , d = date % 100;
5 // date = y * 10000 + m * 100 + d;
6
7 for(int i = y ; ; i ++){
8 if(i % 400 == 0 || (i % 100 != 0 && i % 4 == 0)) month[2] = 29; // 闰年2月有29天
9 else month[2] = 28;
10 int j = (i == y) ? m : 1; // 如果是 i = y,则月份从 m 开始枚举,否则 m 从 1 开始枚举
11 for(; j <= 12 ; j ++){
12 int k = (i == y && j == m) ? d : 1; // 如果 i = y 并且 j = m,则日从 d 开始枚举,否则 d 从 1 开始枚举
13 for(; k <= month[j] ; k ++){
14 // date = i * 10000 + j * 100 + k
15 ...
16 }
17 }
18 }
我们还可以只枚举年份。因为答案一定是个回文日期(即回文串),所以枚举年份 ,再将其翻转得到 ,得到的 就一定是个回文串,如下页图所示。接着再对该串所对应的日期进行合法判断、 ABABBABA 型回文判断即可。
参考代码1-2-4 【枚举合法日期】
1 #include<bits/stdc++.h>
2 using namespace std;
3 int month[13] = {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
4 int date, ans1, ans2;
5 // 判断日期是否为回文
6 bool check1(int date){
7 string s = to_string(date);
8 if(s[0] == s[7] && s[1] == s[6] && s[2] == s[5] && s[3] == s[4]) return true;
9 return false;
10 }
11 // 判断日期是否满足 ABABBABA 型回文
12 bool check2(int date)
13 {
14 string s = to_string(date);
15 if(s[0] == s[2] && s[0] == s[5] && s[0] == s[7] && s[1] == s[3] && s[1] == s[4] && s[1] == s[6]) return true;
16 return false;
17 }
18 signed main()
19 {
20 cin >> date;
21 int y = date / 10000, m = date / 100 % 100, d = date % 100;
22 for(int i = y ; ; i ++) {
23 if(i % 400 == 0 || (i % 100 != 0 && i % 4 == 0)) month[2] = 29;
24 else month[2] = 28;
25 int j = (i == y) ? m : 1;
26 for(; j <= 12 ; j ++) {
27 int k = (i == y && j == m) ? d + 1 : 1;
28 for(; k <= month[j] ; k ++) {
29 int date = i * 10000 + j * 100 + k;
30 if(check1(date) && ans1 == 0) ans1 = date;
31 if(check2(date)) return cout << ans1 << '\n' << date << '\n', 0; // 当找到了 ABABBABA 型回文日期时,结束程序
32 }
33 }
34 }
35 return 0;
36 }
参考代码1-2-5 【枚举年份】
1 #include<bits/stdc++.h>
2 using namespace std;
3 // month[1 ~ 12] 分别记录 1 ~ 12 月每月的天数
4 int date , month[13] = {-1 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31};
5 // 判断日期是否合法
6 string check(int date){
7 string s = to_string(date) , t = to_string(date); // 将整型转换为字符串
8 reverse(t.begin() , t.end()); // 翻转
9 s += t; // 将 s 翻转后再与 s 拼接,拼接后的字符串一定会是个回文串
10 int y = stoi(s.substr(0 , 4)), m = stoi(s.substr(4 , 2)), d = stoi(s.substr(6 , 2));
11 if(y % 400 == 0 || (y % 4 == 0 && y % 100 != 0)) month[2] = 29;
12 else month[2] = 28;
13 if(m < 1 || m > 12 || d < 1 || d > month[m]) return "-1";
14 return s;
15 }
16 // 判断日期是否是 ABABBABA 型回文
17 string check2(int date){
18 string s = to_string(date) , t = to_string(date);
19 reverse(t.begin() , t.end()); // 翻转
20 s += t;
21 if(s[0] == s[2] && s[0] == s[5] && s[0] == s[7] && s[1] == s[3] && s[1] == s[4] && s[1] == s[6]) return s;
22 return "-1";
23 }
24 signed main()
25 {
26 cin >> date;
27 string ans1 = "";
28 for(int i = date / 10000 ; ; i ++){
29 // 注意:date(输入的日期)不能作为答案
30 if(check(i) == "-1" || check(i) == to_string(date)) continue ;
31 if(ans1 == "") ans1 = check(i);
32 if(check2(i) != "-1") return cout << ans1 << '\n' << check2(i) << '\n' , 0;
33 }
34 return 0;
35 }