对一个整数进行分解质因数。
方法一:
暴力:
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);
}
}
}