利用费马小定理判断素数
今天听了ljss神犇的数论课,顿时感觉————我真的是太弱啦!
我只能稍微写一下我能听懂的部分orz
那么这就是今天我为数不多能听懂一点的之一......QAQ
首先先介绍今天的主角:费马小定理
————转自维基百科
没看懂的话我稍微解释一下,就是
假如p是质数,且GCD(a,p)=1,那么 a^(p-1) ≡1(mod p)(假如p是质数,且a,p互质,那么 a的(p-1)次方除以p的余数恒等于1)
因此我们就似乎有了基于费马小定理的判断素数方式:随机枚举使gcd(a,p)=1的a。判断该表达式是否成立--------记为命题q
但是仔细想一想,会发现命题q实际是费马小定理的逆命题
根据我们在高中数学选修2-1学习的内容,真命题的逆命题不一定是真命题....
似乎出现了一些问题呢x
所幸的是,这种思路大部分时间是正确的,因为根据某个奇怪的性质,费马小定理只有对于少数数才会出现逆命题不成立的情况,而这类数就被称为卡迈克尔数(Carmichael number)
卡迈克尔数在正整数中很少,并且随着数的增大会变的越来越少,在1e8范围内只有255个,1e17范围内也才只有不到6e5个,因此可以直接多次应用上述的算法来提高准确性
不过作为有追求的oier,我们怎么能这么没有梦想呢?
我们引入新工具:
二次探测定理 如果p是一个素数,且0<x<p,则方程x^2≡1(mod p)的解必为 x=1或p-1。
下面给出简单的证明:
x^2≡1(mod p)
→x^2-1≡0(mod p)
→(x-1)(x+1)≡0(mod p)
那么我们将二次探测定理转换成
(a(p-1)/2)2≡1(mod p)
应用上面这两个定理可以使失误率达到最劣2-t,而实际远远达不到这个数,因此一般3~5次即可保证正确性
该算法就是Miller_Rabin算法,期望复杂度O(tlog3n)
代码:(还有些许唐突的地方,待补全)题目为洛谷线性筛模板
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<stack> #include<set> #include<map> #include<limits.h> #include<ctime> #define N 10000001 typedef long long ll; const int inf=0x3fffffff; const int maxn=2017; using namespace std; inline int read() { int f=1,x=0;char ch=getchar(); while(ch>'9'|ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch<='9'&&ch>='0') { x=(x<<3)+(x<<1)+ch-'0'; ch=getchar(); } return f*x; } ll qmulti(ll a,ll b,ll c) { ll tem=a,sum=0; while(b) { if(b&1)sum=(sum+tem)%c; tem=(tem+tem)%c; b>>=1; } return sum; } //防止乘的时候过大爆掉 ll qpow(ll a,ll b,ll c) { ll k=1; while(b>0) { if(b&1)k=(k*a)%c; a=(a*a)%c; b>>=1; } return k; } bool witness(int a,int x,int k,int q) { ll v=qpow(a,q,x); if(v==1||v==x-1)return 0; while(k--) { v=v*v%x; if(v==x-1)return 0; } return 1; } bool miller(ll n) { int time=5;//随机time次 if(n==2)return 1;//特判2 if(n<2||n%2==0)return 0; ll a=0,t=0,b=n-1; while(!(b&1)) { t++; b>>=1; } for(int i=0;i<time;i++) { a=rand()%(n-1)+1; if(witness(a,n,t,b))return 0; } return 1; } int main() { srand(time(0)); ll n=read(),m=read(); for(ll i=1;i<=m;i++) { ll a=read(); printf(miller(a)?"Yes\n":"No\n"); } }