前言:
在觀看此博客之前請學習miller_rabin。
我們在分解質因子時也許只會用試根法(也就是暴力)。
而在此我們將學習一個玄學的算法——Pollard_rho。
概念:
Pollard_rho是一種基於隨機的算法,它的思路是先用miller_rabin來判斷當前數是否已經是素數了,如果是的話記錄並返回。如果不是,我們設要分解的數爲n,那麼我們考慮去找一個當前數的因數p,找到之後再分別對p和n/p分解質因數,有一點類似分治但是不是分治。
算法如圖:
那麼,我們要如何快速地找到當前數的因數呢?
這裏,我們採用以下方法:先隨機生成兩個1~n的數,利用這兩個數差的絕對值和n比較,來判斷這兩個數差的絕對值是不是n的一個因數。
可這又是爲什麼?
那麼我們就要講到一個叫生日悖論的東西了。
這裏又出現了一個問題:我們爲什麼要使用兩個隨機數差的絕對值來判斷是否爲n的因數之一,而不是直接生成一個隨機數來進行判斷呢?
這裏我們來講一下生日悖論:
生日悖論,指如果一個房間裏有23個或23個以上的人,那麼至少有兩個人的生日相同的概率要大於50%。這就意味着在一個典型的標準小學班級(30人)中,存在兩人生日相同的可能性更高。對於60或者更多的人,這種概率要大於99%。
如何計算:
第一個人的生日是 365選365
第二個人的生日是 365選364
第三個人的生日是 365選363
第n個人的生日是 365選365-(n-1)
那麼,所有人的生日都不同的概率爲:
所有人中,至少有兩個人生日相同的概率爲:
當n=23時,這個概率爲0.507,n=100時,概率爲0.999999692751072。
當然,選取兩個數也是有道理的。
一開始,我們先隨機生成兩個數x和c,每次都使x=x*x+c(mod n) ,另外再設置一個數y,y的初始值爲x,每進行2^k次操作,就讓y記爲現在的x。在計算的過程中,我們可以發現x=x*x+c(mod n)這個過程是會出現循環的,具體圖像會像希臘字母ρ(rho)一旦出現循環我們還沒有找到可以分解的因數,就意味着這次隨機失敗了,需要我們再次進行隨機。我們爲了記錄什麼時候出現了循環,我們每個2^k次計算就把y記錄成現在的x,如果在接下來的2^k次操作中,出現了y=x,就意味着出現了循環。所以,y的作用一是爲了當隨機的第二個數,二是爲了判斷循環。
代碼如圖:
ll Pollard_rho(ll n1,ll c)//ll 爲long long
{
ll i=1,k=2;
ll x=rand()%n1;
ll y=x;
while(1)
{
i++;
x=(qkch(x,x,n1)+c)%n1;
ll d=gcd(y-x,n1);
if(d>1&&d<n)
return d;
if(x==y)
return n1;
if(i==k)
{
y=x;
k=k*2;
}
}
}
題目描述:
Given a big integer number, you are required to find out whether it's a prime number.
輸入:
The first line contains the number of test cases T (1 <= T <= 20 ), then the following T lines each contains an integer number N (2 <= N < 2 54).
輸出:
For each test case, if N is a prime number, output a line containing the word "Prime", otherwise, output a line containing the smallest prime factor of N.
樣例輸入:
2 5 10
樣例輸出:
Prime 2
此題爲模板題就不需多講。
代碼實現:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<ctime>
#define INF 1ll << 60
using namespace std;
#define ll long long
ll n,ans,t;
ll qkch(ll x,ll y,ll mod)
{
ll res=0;
while(y)
{
if(y&1)
res=(res+x)%mod;
x=(x*2)%mod;
y/=2;
}
return res;
}
ll gcd(ll a,ll b)
{
if(a==0)
return 1;
if(a<0)
return gcd(-a,b);
while(b>0)
{
ll t=a%b;
a=b;
b=t;
}
return a;
}
ll qkpow(ll x,ll y,ll mod)
{
ll ans=1;
while(y)
{
if(y&1)
ans=qkch(ans,x,mod);
x=qkch(x,x,mod);
y/=2;
}
return ans;
}
bool miller_robin(ll n1)
{
if(n1==2)
return 1;
if(n1<2||!(n1&1))
return 0;
ll x,u,pre;
ll k=0;
u=n1-1;
while(!(u&1))
{
k++;
u>>=1;
}
for(int i=1;i<=3;i++)
{
x=rand()%(n1-1)+1;
x=qkpow(x,u,n1);
pre=x;
for(int j=1;j<=k;j++)
{
x=qkch(x,x,n1);
if(x==1&&pre!=n1-1&&pre!=1)
return 0;
pre=x;
}
if(x!=1)
return 0;
}
return 1;
}
ll Pollard_rho(ll n1,ll c)
{
ll i=1,k=2;
ll x=rand()%n1;
ll y=x;
while(1)
{
i++;
x=(qkch(x,x,n1)+c)%n1;
ll d=gcd(y-x,n1);
if(d>1&&d<n)
return d;
if(x==y)
return n1;
if(i==k)
{
y=x;
k=k*2;
}
}
}
void findit(ll p)
{
if(miller_robin(p)==1)
{
if (ans>p)
ans=p;
return;
}
ll p1=p;
while(p1>=p)
{
p1=Pollard_rho(p1,rand()%(p-1)+1);
//printf("%lld %lld\n",p,p1);
}
findit(p1);
findit(p/p1);
}
int main()
{
srand(time(0));
scanf("%lld",&t);
while(t--)
{
ans=INF;
scanf("%lld",&n);
if(miller_robin(n))
printf("Prime\n");
else
{
findit(n);
printf("%lld\n",ans);
}
}
}