質數算法略解


定義

若一個正整數無法被除了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%(不帶高精)

注意在進行素數判斷的時候需要用到快速冪。。

這個應該比較簡單,就不細講了

完整代碼(Luogu P3383 【模板】線性篩素數

#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;
}

二、質數的篩選

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)\)

完整代碼(Luogu P3383 【模板】線性篩素數

#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;
 } 

三、分解質因數

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。

完整代碼(Luogu P4718 【模板】Pollard-Rho算法

#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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM