對一個整數進行分解質因數。
方法一:
暴力:
long long fact[100];
int tot;
void find(long long n)
{
long long len=ceil(sqrt(n+0.0));
for(long long i=2;i<=len;i++)
{
if(n%i==0)
{
fact[++tot]=i;
n/=i;
while(n%i==0)
{
n/=i;
}
}
}
}
方法二:
Pollard Rho算法
時間復雜度為n^0.25
原文請點擊這里
Pollard Rho算法分解一個數n的過程大體上是這樣子的:
1、找到一個數p,使得p|n,將n分解為p與n/p
2、如果p或n/p不為質數,將其帶入遞歸上述過程
3、如果其是質數,將其記錄并退出
是不是很sb。。。有人就會問了:這跟暴力分解有什么區別?好像時間復雜度還比暴力高一些。。。
所以:下面的優化才是關鍵。
第一個優化,使用Miller Rabin算法判定其是否為質數,這個不多提。
關鍵就在于接下來的這個優化。對于一個大整數n,我們要找到一個p滿足p|n,這顯然如大海撈針。但是如果我們要找出p1、p2,使得abs(p1?p2)|n,這看起來似乎要容易一些。實際上我們只需要找出gcd(abs(p1?p2),n)>1的p1、p2,則其gcd值肯定為n的約數。這看起來又容易了一些。
實際上,不止容易一些,而是容易許多。根據某個玄學理論(生日悖論,詳見百度,在此不贅述),這種兩兩比較的方式,在加入比較的數越來越多的時候,其概率會大大提升,比找一個數的概率提升快很多。
于是現在,找p的過程變成了這個樣子:
1、找到一個數p1
2、通過某種玄學推導手段找出一個與p1對應的p2
3、判斷gcd(abs(p1?p2),n)是否為n的因子(1和n本身除外),如果不是則將p2作為新的p1,重復過程,否則就找到了n的因子
怎么又是玄學?因為只有通過推導手段,才能保證不做重復判斷,浪費時間。理論上的推導手段可以有很多,但實際使用中統一使用如下公式推導:
p2=(p1^2+c) mod n
其中c為隨機常數。
這個公式的好處:
1、顯然推導出來的p2-p1差值基本不會相等。
2、可以證明,該推導結果會出現循環。也就是說,在出現循環之前,結果不會重復,少做了許多無用的判斷。
出現循環了怎么辦?換一個隨機常數再搞。這就是該算法“非完美”的地方,如果人品太差那就。。。不過根據上面函數圖像可知,兩個隨機常數產生的推導結果基本不會有重復,所以就可以放心開搞了。
最后一點,判環怎么判?floyd判圈算法搞定。(一個標記以另一個標記幾倍速度走,在環上總能碰到。詳見百度)
需要注意的是,之所以不能一個標記定在原地,是因為循環節不一定在開頭就產生,可能走著走著才遇到循環。這條路徑就類似于ρ,Pollard Rho算法因此得名。
Prime Test
題意:
判定一個數是否素數,如果不是,輸出它的最小質因數
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
// 計算(a * b) % mod
long long multiMod(long long a,long long b,long long mod)
{
long long res=0;
while(b)
{
if(b&1)
{
res=res+a;
if(res>=mod) res-=mod;
}
a=a+a;
if(a>=mod) a-=mod;
b>>=1;
}
return res;
}
// 計算 (a ^ b) % mod
long long powMod(long long a,long long b,long long mod)
{
long long res=1;
while(b)
{
if(b&1)
{
res=multiMod(res,a,mod);
}
a=multiMod(a,a,mod);
b>>=1;
}
return res;
}
// 通過a ^ (n - 1) = 1(mod n)來判斷n是不是素數
// n - 1 = x * 2 ^ t中間使用二次判斷
// 是合數返回true,不一定是合數返回false
bool check(long long a,long long n,long long x,int t)
{
long long res=powMod(a,x,n);
long long last=res;
for(int i=1;i<=t;i++)
{
res=multiMod(res,res,n);
if(res==1&&last!=1&&last!=n-1) return true;//合數
last=res;
}
return res!=1;//最后res=(a^(n-1) % n),如果res!=1,那么不滿足費小馬定理,說明不是素數
}
// 生成[ 0 , n ]的隨機數
long long randomVal(long long n)
{
//rand(): 0~RAND_MAX;
return ((double)rand()/RAND_MAX*n+0.5);
}
// 隨機算法判定次數,一般8~10次就夠了
const int times=8;
// Miller_Rabin算法
// 是素數返回true,(可能是偽素數)
// 不是素數返回false
bool Miller_Rabin(long long n)
{
if(n<2) return false;
if(n==2) return true;
if(!(n&1)) return false;// 偶數(合數)
long long x=n-1,t=0;
while((x&1)==0)
{
t++;
x>>=1;
}
for(int i=0;i<times;i++)
{
long long a=randomVal(n-2)+1;
if(check(a,n,x,t)) return false;
}
return true;
}
long long gcd(long long a,long long b)
{
return b==0?a:gcd(b,a%b);
}
long long Pollard_Rho(long long n,long long c)// 找出n的一個因子
{
int i=1,j=2;
long long x=((double)rand()/RAND_MAX*(n-2)+0.5)+1,y=x;//隨機初始化一個基數
while(true)
{
i++;
x=(multiMod(x,x,n)+c)%n;//玄學遞推
long long val=gcd((y-x+n)%n,n);
if(val!=1&&val!=n) return val;
if(y==x) return FAIL;//y為x的備份,相等則說明遇到圈,退出
if(i==j)
{
y=x;
j<<=1;
}//更新y,判圈算法應用
}
}
long long fact[100];// 質因數分解結果(結果是無序的)
int tot; // 質因數的個數
const int FAIL=-1;
void find(long long n,int c)// 對n進行質因子分解
{
if(n==1) return ;
if(Miller_Rabin(n))
{
fact[++tot]=n;
return;
}
long long x=FAIL;
int k=c;
while(x==FAIL)
{
x=Pollard_Rho(n,k--);
}
find(n/x,c);
find(x,c);
}
const int INF=1e18;
int main()
{
int t;
scanf("%d",&t);
long long n,ans;
while(t--)
{
scanf("%lld",&n);
tot=0;
if(Miller_Rabin(n)) printf("Prime\n");
else
{
find(n,120);
ans=INF;
for(int i=1;i<=tot;i++)
{
ans=min(ans,fact[i]);
}
printf("%lld\n",ans);
}
}
}