NOI.1.13.11 回文素数
NOI.1.13.11 回文素数 (C++)
全局题号2248,题目原址:http://noi.openjudge.cn/ch0113/11/
这道题可以取到9位数,导致我一开始用筛选质数表记录,结果空间limited,改为用较高效的素数判断法也超时。看了一大哥帖子思路后瞬间点醒(那帖子找不到链接了道歉)(找到了在后面) 。同时判断回文和素数,老老实实枚举下去容易超时,要缩小解集快速进行某一步(要么耍流氓直接用已用质数表,要么生成出n位的回文数不再判断回文)。素数判断用到了数论的知识略过了很多解空间,同样回文素数后来debug发现了n大于2的偶数位下存在的规律。代码如下,一些解释看后头
#include<iostream>
#include<vector>
using namespace std;
bool isPrime(int n){ //素数存在定理,大于3的素数必处于6的倍数两端
if(n<=3)return n>1;
if(n%6!=1&&n%6!=5)return false; //不为6倍数附近则不是素数
for(int i = 5 ; i*i<=n;i+=6){ //以6的倍数递增,收敛在sqrt n
if(n%i==0||n%(i+2)==0)return false;
}
return true;
}
void generatePal(vector<int>&table, int x, int y,int N){ //将x~y之间的数倒序加入table
if(N>2&&N%2==0)return ; //跳过所有大于2的偶数次位,必为11的倍数,数量为0
int f,k;
f = N%2==1?10:1; //f 控制奇偶情况的不同处理方式
k = N/2; //k指明乘几次10,以给后续留出位置
for(int i = x ;i<=y;i++){ //x~y之间的数的中心对称
int s = i ,m = i/f, t = 0 ; //s为动态结果串,m为余数提取串
for(int j = 0; j<k;j++){
t = m%10+t*10; //余数积累反转
m/=10;
s*=10;
}
s+=t; //余数提取加入结果串,入队
table.push_back(s);
}
}
int main(){
int res=0, N,n;cin>>N;
n = (N+1)/2; // n为N的折半位数
int X = 1, Y = 0; //X,Y经处理后变为半N位数下的取数范围 [ X, Y ]
while(n>0){
X = 10*X;
Y = 10*Y+9;
n--;
}
X/=10;
vector<int> Res, tab;
generatePal(tab,X,Y,N); //生成N位的回文数组
for(int i = 0;i<tab.size();i++){
if(isPrime(tab[i])){ //为素数,统计,入队
res++;
Res.push_back(tab[i]);
}
}
//结果输出
cout<<res<<endl;
for(int i = 0;i<Res.size();i++){
cout<<Res[i]<<' ';
}
return 0;
}
查看5位数的回文串时,数字关于第3位开始中心对称,如102->10201,这么看来按3位数100开始穷举到999进行中心对称就可以列出5位数的所有回文数(从10000~99999的9w次回文判断到从3位数对称9伯次是很大的飞跃!)
同样的6位数是前3位的中心对称,如102->102201。位数奇偶会影响一点生成回文串的操作,代码中我用f来进行控制,草稿上演算一下其实就是中间那位要不要取余提取。
最后调试发现n=4,6,8,res都是0,起初不敢相信4位数里竟然没有回文素数,几番查找,回想起当初知道素数分布规律时的震撼,应该可能大概是同一种方法吧。拆了拆试试,果不其然,这里粗略证明。任意4位数可看作abba,按十进制拆开就为1000a+100b+10b+1a,同类项合并后为1001a+110b,于是可提取11*(91a+10b),即4位数回文串均是11倍数。同理6,8位也能提出11倍来。代码中就可以跳过了大于2的偶数位回文数生成了!
至于素数的分布,就是大于等于5以上的数字都能写成6n-1,6n,6n+1,6n+2,6n+3,6n+4,6(n+1)-1的形式,其中6n,6n+2,6n+3,6n+4明显能提出公因式2,3。即大于等于5以上的数若是素数必分布在6倍数附近(第一次知道的时候好塔玛兴奋)素数判断就按6倍数周围跳着走啦。