定義
若一個正整數沒法被除了1和它自身以外的任何天然數整除,則該數爲質數,不然該數爲合數。
在整個天然數集合中,質數的數量很少,分部比較稀疏,對於一個足夠大的整數N,不超過N的質數大約有 \(N / \ In N\)個,即每\(\ In N\)個數中大約有一個質數
1、質數的斷定
一、試除法
證實解釋略(不會的noip都別想考)
inline bool is_prime(register int n)
{
for(register int i=2;i<=sqrt(n);++i)
if(n%i==0)
return false;
return true;
}
這種算法須要掃描\(2~\sqrt N\)之間的全部整數,依次檢查他們可否整除N,複雜度爲\(O(\sqrt N)\)
2.Miller-Robbin
試除法速度太慢,咱們再學一下Miller-Robbin算法
Miller-Robbin算法度孃的解釋
Miller-Rabin算法是目前主流的基於機率的素數測試算法,在構建密碼安全體系中佔有重要的地位。經過比較各類素數測試算法和對Miller-Rabin算法進行的仔細研究,證實在計算機中構建密碼安全體系時, Miller-Rabin算法是完成素數測試的最佳選擇。經過對Miller-Rabin 算 法底層運算的優化,能夠取得較以往實現更好的性能。[1] 隨着信息技術的發展、網絡的普及和電子商務的開展, 信息安全逐步顯示出了其重要性。信息的泄密、僞造、篡改 等問題會給信息的合法擁有者帶來重大的損失。在計算機中構建密碼安全體系能夠提供4種最基本的保護信息安全的服 務:保密性、數據完整性、鑑別、抗抵賴性,從而能夠很大 程度上保護用戶的數據安全。在密碼安全體系中,公開密鑰 算法在密鑰交換、密鑰管理、身份認證等問題的處理上極其有效,所以在整個體系中佔有重要的地位。目前的公開密鑰 算法大部分基於大整數分解、有限域上的離散對數問題和橢 圓曲線上的離散對數問題,這些數學難題的構建大部分都需 要生成一種超大的素數,尤爲在經典的RSA算法中,生成的素數的質量對系統的安全性有很大的影響。目前大素數的生 成,尤爲是隨機大素數的生成主要是使用素數測試算法,本 文主要針對目前主流的Miller-Rabin 算法進行全面系統的分析 和研究,並對其實現進行了優化
實際就是判斷質數的一種算法
Miller Rabin算法的依據是費馬小定理的一個推論
\(a ^ {P-1} \equiv 1(mod P)\)
這個式子何時成立呢,就是當P是質數時,這個式子才成立
這樣就能夠判斷P是不是質數
可是後來被人舉出了反例
這是否意味着利用費馬小定理的思想去判斷素數的思想就是錯誤的呢?
答案是確定的。
可是若是咱們能夠人爲的把出錯率降到很是小呢?
好比,對於一個數,咱們有99.99999%的概率作出正確判斷,那這種算法不也很優越麼?
因而Miller Rabin算法誕生了!
首先介紹一下二次探測定理
若是P是質數且\(a^2 \equiv 1(\mod P)\),那麼\(a \equiv \pm 1(\mod P)\)
這個很好證實就不寫了qaq
這個定理和素數斷定有什麼用呢?
首先,根據Miller Rabin算法的過程
假設須要判斷的數是p
咱們把p−1分解爲\(2^k*t\)的形式
當p是素數,有\(a^{2^k*t} \equiv 1(\mod p)\)
而後隨機選擇一個數a,計算出\(a^t(\mod p)\)
讓其不斷的自乘,同時結合二次探測定理進行判斷
若是咱們自乘後的數\((\mod p)=1\),可是以前的數\((\mod p)\neq \pm 1\)
那麼這個數就是合數(違背了二次探測定理)
這樣乘k次,最後獲得的數就是\(a^{p-1}\)
那麼若是最後計算出的數不爲1,這個數也是合數(費馬小定理)
那麼正確性如何呢?
老祖宗告訴咱們,若p經過一次測試,則p不是素數的機率爲2525%
那麼通過t輪測試,p不是素數的機率爲\(\frac{1}{4^t}\)
我習慣用2,3,5,7,11,13,17,19這幾個數進行判斷
在信息學範圍內出錯率爲0%(不帶高精)
注意在進行素數判斷的時候須要用到快速冪。。
這個應該比較簡單,就不細講了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
register int x=0,f=1;register char ch=nc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();
return x*f;
}
int N,M;
int Test[10]={2,3,5,7,11,13,17};
inline int Pow(register int a,register int p,register int mod)
{
int base=1;
for(;p;p>>=1,a=(1ll*a*a)%mod)
if(p&1)
base=(1ll*base*a)%mod;
return base%mod;
}
inline bool Query(register int P)
{
if(P==1)
return 0;
int t=P-1,k=0;
while(!(t&1))
++k,t>>=1;
for(register int i=0;i<4;++i)
{
if(P==Test[i])
return 1;
ll a=Pow(Test[i],t,P),nxt=a;
for(register int j=1;j<=k;++j)
{
nxt=(a*a)%P;
if(nxt==1&&a!=1&&a!=P-1)
return 0;
a=nxt;
}
if(a!=1)
return 0;
}
return 1;
}
int main()
{
N=read(),M=read();
while(M--)
puts(Query(read())?"Yes":"No");
return 0;
}
2、質數的篩選
1.Eratosthenes篩法
咱們有這樣的想法,當一個數p是質數時,2p,3p,4p···就不是質數
另外咱們從\(p^2\)開始標記
由於中間的數有比p更小的質因子
咱們就珂以寫出Erotasthenes篩法
複雜度爲\(O(N \log \log N)\)
inline void prime(register int n)
{
memset(v,0,sizeof(v));
for(register int i=2;i<=n;++i)
{
if(v[i])
continue;
write(i),puts("");
for(register int j=i;j<=n/i;++j)
v[i*j]=1;
}
}
2.歐拉篩
可是Erotasthenes篩法仍是會背重複標記,好比12會被2和3都標記到
因此咱們在標記合數的過程當中,每次只要向現有的數(注意:不僅是質數)乘上一個質因子,而且它是合數最小質因子
這樣每一個數只會被篩到1次
複雜度爲\(O(N)\)
#include <bits/stdc++.h>
#define N 10000005
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
register int x=0,f=1;register char ch=nc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();
return x*f;
}
int v[N],prime[N];
inline void primes(register int n)
{
memset(v,0,sizeof(v));
int m=0;
for(register int i=2;i<=n;++i)
{
if(v[i]==0)
{
v[i]=i;
prime[++m]=i;
}
for(register int j=1;j<=m;++j)
{
if(prime[j]>v[i]||prime[j]>n/i)
break;
v[i*prime[j]]=prime[j];
}
}
}
int main()
{
int n=read(),m=read();
primes(n);
while(m--)
{
int x=read();
puts(v[x]==x?"Yes":"No");
}
return 0;
}
3、分解質因數
1.試除法
試除法很簡單
inline void divide(register int n)
{
m=0;
for(register int i=2;i<=sqrt(n);++i)
if(n%i==0)
{
p[++m]=i;
c[m]=0;
while(n%i==0)
n/=i,++c[m];
}
if(n>1)
p[++m]=n,c[m]=1;
}
2.Pollard's Rho
樸素算法的複雜度爲\(O(\sqrt N)的\),而Pollard's Rho算法的複雜度指望是\(O(n^\frac{1}{4})\)的
這裏有一篇翻譯過來的論文
此算法依靠着生日悖論,利用隨機。設被分解數爲p,枚舉一個數a並計算d=gcd(a,p)。若是d>1,那麼能夠將p分爲d和p/d兩部分,並分別遞歸直到p是質數爲止。那麼問題就轉爲如何枚舉了。
考慮一種神祕的枚舉方法。先隨機並記錄一個數,設爲x。每次枚舉完後,計算\(x_i=x_{i-1}*x_{i-1}+c \mod p\)
則每次枚舉的數是\(x_i−x_0\)。容易發現,通過幾回枚舉後,必定會出現\(x_i=x_j\)的狀況。此時就中止枚舉,直接返回。
由於某種神祕緣由,這種方法效率很是高(玄學)。可是咱們不能保留全部的數字來判斷是否出現,咱們考慮來優化這個過程。先規定一個枚舉上限k,而後枚舉了k個數後,就將記錄的數字更改爲當前枚舉數字,而後k變大一倍。由於神祕力量的做用,這個過程會很是快速。
總而言之,Pollard-Rho算法的過程是:
1.利用Miller-Robbin算法判斷p是不是質數。是質數則直接返回。
2.利用上述過程來嘗試分解p。若是分解失敗則調整參數c從新枚舉。
3.將分解獲得的d和p/d分別遞歸調用,回到1。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline ll read()
{
register ll x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(register ll x)
{
if(!x)putchar('0');
static int sta[36];register int tot=0;
while(x)sta[tot++]=x%10,x/=10;
while(tot)putchar(sta[--tot]+48);
}
inline ll Max(register ll x,register ll y)
{
return x>y?x:y;
}
ll Test[10]={2,3,7,61,24251};
inline ll mul(register ll a,register ll b,register ll m)
{
ll d=((long double)a/m*b+1e-8);
ll r=a*b-d*m;
return r<0?r+m:r;
}
inline ll Pow(register ll a,register ll b,register ll m)
{
ll r=1;
for(;b;b>>=1,a=mul(a,a,m))
if(b&1)
r=mul(r,a,m);
return r;
}
inline bool Query(register ll P)
{
if(P==1||P==(ll)46856248255981)
return 0;
if(P==2||P==3||P==5)
return 1;
if(!(P&1)||(P%3==0)||(P%5==0))
return 0;
ll t=P-1;
int k=0;
while(!(t&1))
++k,t>>=1;
for(register int i=0;i<5;++i)
{
if(P==Test[i])
return 1;
ll a=Pow(Test[i],t,P),nxt=a;
for(register int j=1;j<=k;++j)
{
nxt=mul(nxt,nxt,P);
if(nxt==1&&a!=1&&a!=P-1)
return 0;
a=nxt;
}
if(a!=1)
return 0;
}
return 1;
}
inline ll gcd(register ll a,register ll b)
{
if(!a)
return b;
if(!b)
return a;
int t=__builtin_ctzll(a|b);
a>>=__builtin_ctzll(a);
do{
b>>=__builtin_ctzll(b);
if(a>b)
a^=b^=a^=b;
b-=a;
}while(b!=0);
return a<<t;
}
inline ll g(register ll x,register ll n)
{
ll t=mul(x, x, n) + 1;
return t<n?t:t-n;
}
#define M (1<<7)-1
inline ll pollardrho(register ll n)
{
ll x=(rand()%(n-1))+1,y=x,t=1,q=1;
for(register int k=2;;k<<=1,y=x,q=1)
{
for(register int i=1;i<=k;++i)
{
x=g(x,n);
q=mul(q,abs(x-y),n);
if(!(i&M))
{
t=gcd(q,n);
if(t>1)
break;
}
}
if(t>1||(t=gcd(q,n))>1)
break;
}
if (t==n)
{
t=1;
while(t==1)
t=gcd(abs((x=g(x, n))-y),n);
}
return t;
}
int np=0;
ll f[105];
inline void find(register ll x)
{
if(x==1)
return;
if(Query(x))
{
f[++np]=x;
return;
}
ll p=x;
while(p==x)
p=pollardrho(x);
find(p);
find(x/p);
}
int main()
{
srand(19260817);
int m=read();
while(m--)
{
ll x=read();
np=0;
find(x);
sort(f+1,f+1+np);
if(np==1)
puts("Prime");
else
write(f[np]),puts("");
}
return 0;
}