Miller_Rabin素数测试和Pollard Rho大数质因数分解

Pollard Rho大数质因数分解

基本思想

  对于一个大整数 \(n\) ,随机取一个数是它的质因子的概率很小。但如果取两个数并且规定他们的差值为 \(n\) 的因数,那么这个概率就会提高。
  对于满足 \(gcd(abs(x_1−x_2),n)>1\)\(x_1\)\(x_2\)\(gcd(abs(x_1−x_2),n)\) 就是 \(n\) 的一个因数,只需要判断它是否为素数,若为素数,则是 \(n\) 的质因数,否则递归此过程。其中判断素数就使用 Miller-Rabin 算法。\(x_1\)在区间 \([1,n]\) 中随机出来,而 \(x_2\) 则由 \(x[i]=(x[i-1]*x[i-1]%n+c)%n\) 推算出来,其中 \(c\)为任意给定值,事实证明,这样就是比较优的。

算法流程

  我们先用 Miller-Rabin 判断当前的数是否是素数, 如果是则直接返回,否则找出这个数的一个因子(不一定是质因子),然后递归地分解这个因子和约掉这个因子的另一个因子。 然而在找出这个数的因子时,Pollard Rho 算法利用了一个随机化算法: 我们通过随机选取一个整数 \(a\),然后 \(b=a×a+c\) (\(c\)为任意整数)得到两个整数 \(a\)\(b\), 设待分解的整数为 \(n\),那么循环计算 \(p=gcd(abs (a-b),n)\), 若 \(p\) 不为 \(1\)\(a\)\(b\) 出现循环则跳出, 否则需要将 \(b\) 的值赋给 \(a\),再次利用 \(b=a×a+c\) 计算新的 \(b\) 值。 若 \(a,b\) 出现循环,我们需要更换 \(c\) 值再次重新循环计算; 而若 \(1 < p < n\) ,则找到了一个因子 \(p\)。然后还有一个小小的优化,就是利用 \(Floyd\) 判环的原理,让 \(A\)\(B\) 按照 \(B\) 的速度是 \(A\) 的速度的两倍从同一起点开始往前走 当 \(B\) 第一次赶上 \(A\) 时,\(B\) 已经走了至少一圈,利用这个优化可以快很多。

Miller_Rabin素数测试

基于费马小定理的一个拓展。

费马小定理:对于质数 \(p\),任意整数 \(a\),均满足:\(a^p≡a(mod\ p)\)
逆命题为:若任意正整数 \(n\) 满足 \(a^n ≡ a (mod\ n)\),则 \(n\) 为素数。(错误

  因此,假设我们要测试 \(n\) 是否为质数。我们可以随机选取一个数 \(a\),然后计算 \(a^{(n-1)} mod\ n\),如果结果不为 \(1\),我们可以断定 \(n\) 不是质数。即费马定理的逆命题。

Fermat 测试(费马测试):随机选取一个新的数 \(a\) 进行测试,如此反复多次,如果每次结果都是 \(1\),我们就假定 \(n\) 是质数。

  但 Fermat 测试不一定是准确的,有可能出现把合数误判为质数的情况,如伪素数
  原先在费马小定理逆定理上的研究只考虑了 \(a=2\) 的情况,但是对于式子 \(a^(n-1) mod\ n\),取不同的 \(a\) 可能导致不同的结果:一个合数可能在 \(a=2\) 时通过了测试,但 \(a=3\) 时的计算结果却排除了素数的可能。于是,人们扩展了伪素数的定义,称满足 \(a^(n-1) mod\ n = 1\) 的合数 \(n\) 叫做以 \(a\) 为底的伪素数。前 \(10\) 亿个自然数中同时以 \(2\)\(3\) 为底的伪素数只有 \(1272\) 个,这个数目不到仅仅以 \(2\) 为底的伪素数的 \(1/4\)。这告诉我们如果同时验证 \(a=2\)\(a=3\) 两种情况,出错的概率降到了 \(0.0025%\) ,那么以此类推,测试的数越多,出错的可能性越小(不能降低到0)。
二次探测定理

如果 \(p\) 是奇素数,则 \(x^2 ≡ 1(mod\ p)\) 的解为 \(x ≡ 1\)\(x ≡ p - 1(mod\ p)\)

利用二次探测定理优化,那么算法就变成了:
  尽可能提取因子 \(2\),把 \(n-1\) 表示成 \(d*2^t\)。然后令 \(x[0]=a^d mod\ p\),那么将 \(x[0]\) 平方 \(t\) 次就是 \((a^d)^{2^t}mod\ p\) 的值,我们设 \(x[i]\)\(x[0]\) 平方 \(i\) 次的值,根据二次探测定理,若 \(x[i]\) 等于1,则 \(x[i−1]\) 等于 \(1\)\(p-1\),不满足则 \(p\) 为合数。

时间复杂度:考虑常数后为 \(O(slog_3n)\)

模板代码

poj 1811
判断一个大数是否是一个素数,如果不是则输出其最小的质因子。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1000;//数组大小
const int S=1000;//素数测试时的变换次数
typedef long long ll;
int tol;//分解的质因子的个数
ll factor[N];//存分解的质因子
ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):a;
}
ll mul(ll a,ll b,ll p)
{
    ll res=0;
    while(b)
    {
        if(b&1) res=(res+a)%p;
        a=(a+a)%p;
        b>>=1;
    }
    return res;
}
ll power(ll a,ll b,ll p)
{
    ll res=1;
    while(b)
    {
        if(b&1) res=mul(res,a,p);
        a=mul(a,a,p);
        b>>=1;
    }
    return res;
}
//素数测试部分:
bool check(ll a,ll n,ll d,ll cnt)
{
    ll res=power(a,d,n);//a^d%n
    ll last=res;
    for(int i=1;i<=cnt;i++)
    {
        res=mul(res,res,n);
        if(res==1&&last!=1&&last!=n-1)//
            return true;
        last=res;//
    }
    if(res!=1) return true;//
    return false;
}
bool Miller_Rabin(ll n)//判断一个大数是否是素数
{
    if(n<2) return false;
    if(n==2) return true;
    if((n&1)==0) return false;
    ll d=n-1,cnt=0;
    while(d%2==0)//提取因子2
        d>>=1,cnt++;
    for(int i=1;i<=S;i++)
    {
        ll a=rand()%(n-1)+1;
        if(check(a,n,d,cnt)) 
            return false;//不是素数
    }
    return true;
}

//质因子分解部分:
ll pollard_rho(ll n,ll k)
{
    ll u=2,v=1;
    ll x=rand()%(n-1)+1;
    ll y=x;
    while(true)
    {
        x=(mul(x,x,n)+k)%n;
        v++;
        ll d=gcd((y-x+n)%n,n);
        if(1<d&&d<=n) return d;
        if(v==u)//floyd 判环优化
        {
            y=x;
            u<<=1;
        }
    }
}
//对n进行质因数分解,存入factor,k设置为127左右基本满足
void divide(ll n,ll k)
{
    if(n==1) return;
    if(Miller_Rabin(n))//是素数
    {
        factor[++tol]=n;
        return;
    }
    ll d=n,c=k;
    while(d==n) d=pollard_rho(n,--c);
    divide(d,c);
    divide(n/d,c);
}
int main()
{
    ll n;
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld",&n);
        tol=0;
        if(Miller_Rabin(n)) printf("Prime\n");
        else
        {
            divide(n,127*1LL);
            ll ans=n;
            for(int i=1;i<=tol;i++)
                ans=min(ans,factor[i]);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/whix/p/13194489.html