正题
题目链接:https://loj.ac/p/143
题目大意
给出一个数ppp,让你判定是否为质数。
解题思路
Miller−RabinMiller-RabinMiller−Rabin是一种基于费马小定理和二次探测定理的具有较高正确性的高效质数判定算法。
首先讲一下两个定理
- 费马小定理:gcd(a,p)=1⇒ap−1=1(modp)gcd(a,p)=1\ \ \ \Rightarrow\ \ \ a^{p-1}=1(mod\ p)gcd(a,p)=1 ⇒ ap−1=1(mod p)
- 二次探测定理:若ppp是一个素数且有0<x<p0<x<p0<x<p那么有xn=1(modp)⇒n=1orp−1x^n=1(mod\ p)\ \ \ \Rightarrow\ \ \ n=1\ or\ p-1xn=1(mod p) ⇒ n=1 or p−1
这两个定理我们怎么使用呢,我们先将p−1p-1p−1分解成2st2^st2st的形式,这样我们对于一个数ata^tat就可以进行sss次平方将其变为ap−1a^{p-1}ap−1。
再选取一个较小的质数aaa,然后不停将ata^tat平方,每平方一次就使用一次二次探测定理来判定质数。知道ata^tat平方sss次后变为ap−1a^{p-1}ap−1就再用一次费马小定理。
当然这样无法完全保证正确性,但是如果我们多拿几个质数试一试就可以大大缩小错误概率。并且目前可以证明在intintint范围内使用前303030个质数是保证不会出错的,但是一般代码中为了确保效率会使用少一些素数。
注意使用longlonglong\ longlong long时乘数可能会超过范围,所以可以用黑科技O(1)O(1)O(1)的快速乘来解决
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll pri[20]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71};
ll ksc(ll a,ll b,ll p){a%=p;b%=p;ll c=(long double)a*b/p;long double ans=a*b-c*p;if(ans<0)ans+=p;else if(ans>=p)ans-=p;return ans;
}
ll power(ll x,ll b,ll p){ll ans=1;while(b){if(b&1)ans=ksc(ans,x,p);x=ksc(x,x,p);b>>=1;}return ans;
}
bool Mr(ll p){if(p==2)return 1;if(p<2||!(p&1))return 0;ll s=0,t=p-1;while(!(t&1))s++,t>>=1;for(ll i=0;i<10&&pri[i]<p;i++){ll x=power(pri[i],t,p),k;for(ll j=0;j<s;j++){k=ksc(x,x,p);if(k==1&&x!=1&&x!=p-1)return 0;x=k;}if(x!=1)return 0;}return 1;
}
int main()
{ll x;while(scanf("%lld",&x)!=EOF){if(Mr(x))printf("Y\n");else printf("N\n");}
}