记录41
#include<bits/stdc++.h>
using namespace std;
bool f(int a){
int t=0,n=4;
while(n--){
t=t*10+a%10;
a/=10;
}
if(a==t) return true;
else return false;
}
int main(){
int y1,y2,cnt=0;
cin>>y1>>y2;
for(int i=y1;i<=y2;i++){
int y=i/10000;
int m=i/1000%10*10+i/100%10;
int d=i/10%10*10+i%10;
if(m==0||m>12||d==0||d>31) continue;
if(f(i)){
//cout<<i<<" "<<m<<" "<<d<<endl;
if(m==2){
if(y%4==0&&y%100!=0||y%400==0){
if(d>29) continue;
}
else if(d>28) continue;
}
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12){
if(d>31) continue;
}
if(m==4||m==6||m==9||m==11){
if(d>30) continue;
}
cnt++;
}
}
cout<<cnt;
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P2010
突破点
例如:
- 对于 2016 年 11 月 19 日,用 8 位数字 20161119 表示,它不是回文的。
- 对于 2010 年 1 月 2 日,用 8 位数字 20100102 表示,它是回文的。
- 对于 2010 年 10 月 2 日,用 8 位数字 20101002 表示,它不是回文的。
思路
- 把数字后四位倒着取出来
- 判断是否回文
- 判断是否为正常日期
#include<bits/stdc++.h>
using namespace std;
bool f(int a){
int t=0,n=4;
while(n--){
t=t*10+a%10;
a/=10;
}
if(a==t) return true;
else return false;
}
int main(){
...
return 0;
}
f函数判断8位数是否回文
关于回文的一些处理技巧,放在文章的补充部分
#include<bits/stdc++.h>
using namespace std;
bool f(int a){
int t=0,n=4;
while(n--){
t=t*10+a%10;
a/=10;
}
if(a==t) return true;
else return false;
}
int main(){
int y1,y2,cnt=0;
cin>>y1>>y2;
for(int i=y1;i<=y2;i++){
int y=i/10000;
int m=i/1000%10*10+i/100%10;
int d=i/10%10*10+i%10;
if(m==0||m>12||d==0||d>31) continue;
if(f(i)){
...
}
}
cout<<cnt;
return 0;
}
y1 记录开始时间
y2 记录结束时间
cnt 记录符合的个数
y 代表年 m 代表月 d 代表日
if(m==0||m>12||d==0||d>31) continue; 排除明显错误的日期
#include<bits/stdc++.h>
using namespace std;
bool f(int a){
int t=0,n=4;
while(n--){
t=t*10+a%10;
a/=10;
}
if(a==t) return true;
else return false;
}
int main(){
int y1,y2,cnt=0;
cin>>y1>>y2;
for(int i=y1;i<=y2;i++){
int y=i/10000;
int m=i/1000%10*10+i/100%10;
int d=i/10%10*10+i%10;
if(m==0||m>12||d==0||d>31) continue;
if(f(i)){
//cout<<i<<" "<<m<<" "<<d<<endl;
if(m==2){
if(y%4==0&&y%100!=0||y%400==0){
if(d>29) continue;
}
else if(d>28) continue;
}
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12){
if(d>31) continue;
}
if(m==4||m==6||m==9||m==11){
if(d>30) continue;
}
cnt++;
}
}
cout<<cnt;
return 0;
}
排除对应的年月日错误的日期
补充
在CSP-J竞赛中,回文处理是字符串算法的常见考点。以下是从基础到进阶:
1. 回文基础定义与性质
回文:正读反读都相同的字符串或数字,如
"level","1221"。核心性质:
对称性:位置
i的字符等于位置n-1-i的字符奇偶性:长度为奇数或偶数时,中心点处理不同
2. CSP-J高频题型分类
题型 难度 出现频率 示例 回文判断 ★☆☆☆☆ 极高 判断字符串/数字是否为回文 最长回文子串 ★★★☆☆ 高 求字符串中最长回文子串长度 回文串计数 ★★☆☆☆ 中 统计回文子串/回文数个数 构造回文 ★★☆☆☆ 中 添加最少字符使其成为回文 回文串修改 ★★★☆☆ 低 修改最少字符得到回文
3. 基础判断手段(双指针法)
模板1:判断字符串回文(O(n))
bool isPalindrome(const string& s) { int left = 0, right = s.size() - 1; while (left < right) { if (s[left] != s[right]) return false; left++; right--; } return true; }竞赛技巧:跳过非字母数字字符和大小写,预处理后再判断。
模板2:判断数字回文(O(log n))
bool isPalindrome(int x) { if (x < 0 || (x % 10 == 0 && x != 0)) return false; int rev = 0; while (x > rev) { rev = rev * 10 + x % 10; x /= 10; } return x == rev || x == rev / 10; }CSP-J应用:常结合数位DP或枚举判断回文数。
4. 进阶处理手段
方法一:暴力枚举 + 双指针验证(CSP-J够用)
// 求最长回文子串(O(n²)) int longestPalindrome(const string& s) { int maxLen = 0; for (int center = 0; center < s.size(); center++) { // 奇数长度回文 int len1 = expand(s, center, center); // 偶数长度回文 int len2 = expand(s, center, center + 1); maxLen = max(maxLen, max(len1, len2)); } return maxLen; } int expand(const string& s, int left, int right) { while (left >= 0 && right < s.size() && s[left] == s[right]) { left--; right++; } return right - left - 1; // 返回长度 }CSP-J适用性:
n ≤ 1000时完全够用,代码短易写。方法二:动态规划(O(n²),更通用)
// dp[i][j] 表示s[i..j]是否为回文 bool dp[1005][1005]; // n ≤ 1000 void longestPalindromeDP(const string& s) { int n = s.size(), maxLen = 0; for (int i = n - 1; i >= 0; i--) { dp[i][i] = true; // 单个字符是回文 for (int j = i + 1; j < n; j++) { if (s[i] == s[j]) { if (j - i == 1 || dp[i+1][j-1]) { dp[i][j] = true; maxLen = max(maxLen, j - i + 1); } } } } }竞赛技巧:从下往上填表,避免重复计算。
方法三:Manacher算法(O(n),CSP-S级别)
// 预处理:插入'#'分隔符,统一奇偶 string preprocess(const string& s) { string t = "^#"; for (char c : s) { t += c; t += '#'; } t += '$'; return t; } int manacher(const string& s) { string t = preprocess(s); int n = t.size(), center = 0, right = 0; vector<int> p(n, 0); // p[i]表示以i为中心的回文半径 for (int i = 1; i < n - 1; i++) { if (i < right) { p[i] = min(right - i, p[2 * center - i]); // 对称性优化 } // 中心扩展 while (t[i + p[i] + 1] == t[i - p[i] - 1]) { p[i]++; } // 更新最右边界 if (i + p[i] > right) { center = i; right = i + p[i]; } } return *max_element(p.begin(), p.end()); // 最长回文半径 }CSP-J适用性:J组几乎不考,S组可能涉及。理解思想即可,代码较长。
5. 竞赛实用技巧
技巧1:预处理字符串
// 统一大小写和非法字符 string preprocess(const string& s) { string res; for (char c : s) { if (isalnum(c)) { // 保留字母数字 res += tolower(c); } } return res; }技巧2:回文串快速判断(空间换时间)
// 预处理哈希值,O(1)判断任意子串回文 // 正序哈希 + 逆序哈希技巧3:构造回文的最小代价
// 求最小插入字符数 = 原串长度 - 最长回文子序列长度 int minInsertions(string s) { int n = s.size(); vector<vector<int>> dp(n, vector<int>(n, 0)); for (int i = n - 1; i >= 0; i--) { dp[i][i] = 1; for (int j = i + 1; j < n; j++) { if (s[i] == s[j]) { dp[i][j] = dp[i+1][j-1] + 2; } else { dp[i][j] = max(dp[i+1][j], dp[i][j-1]); } } } return n - dp[0][n-1]; // 最少插入字符数 }
6. 易错点与注意事项
易错点 错误示例 正确做法 索引越界 s[i] == s[n-i]s[i] == s[n-1-i]奇偶混淆 只考虑奇数长度回文 分别处理奇数/偶数中心 空串处理 未判断 s.empty()单独处理空串返回 true或0负数回文 未处理负号 负数直接返回 false大小写敏感 "Aa"误判为非回文预处理统一大小写
7. CSP-J高频模板(直接背诵)
// 模板1:判断字符串回文 bool isPalindrome(const string& s) { int l = 0, r = s.size() - 1; while (l < r) if (s[l++] != s[r--]) return false; return true; } // 模板2:最长回文子串(中心扩展) int expand(const string& s, int l, int r) { while (l >= 0 && r < s.size() && s[l] == s[r]) l--, r++; return r - l - 1; } int longestPalindrome(const string& s) { int n = s.size(), ans = 0; for (int i = 0; i < n; i++) { ans = max(ans, expand(s, i, i)); // 奇数长度 ans = max(ans, expand(s, i, i + 1)); // 偶数长度 } return ans; } // 模板3:数字回文判断 bool isPalindrome(int x) { if (x < 0 || (x % 10 == 0 && x != 0)) return false; int rev = 0; while (x > rev) rev = rev * 10 + x % 10, x /= 10; return x == rev || x == rev / 10; }
8. 总结与竞赛策略
CSP-J层面:
掌握双指针判断:O(n)复杂度,代码5行
掌握中心扩展法:O(n²)求最长回文,代码20行内
会预处理字符串:统一大小写、删非法字符
避免Manacher:代码复杂,性价比低
时间分配:
回文判断:5分钟写完
最长回文子串:15分钟暴力+调试
构造类问题:结合DP,预留30分钟
核心箴言:回文问题对称性是灵魂,双指针是万能钥匙,空间换时间是常见优化手段。
回文日期问题解析与技巧
3075

被折叠的 条评论
为什么被折叠?



