Pollard-Rho 分解算法学习笔记

Pollard-Rho 分解算法

Pollard-Rho 算法是一种用于快速找到n的一个非平凡约数的方法。

生日悖论

在不少于23个人中至少有两人生日相同的概率已经大于50%

更一般的形式,随机选取在[1,N]范围内的整数,期望到第O(N)个出现重复。

用下面的方法可以简单估计一下。

P(A)=1nn×n1n××nk+1n12(11n)×(12n)××(1k1n)12根据不等式1+xex(11n)×(12n)××(1k1n)ek(k1)2n12k2nln2

Miller-Robin 素性测试

由于Pollard-Rho是用来寻找非平凡因子的,我们需要提前判断待分解数是否为素数。

费马小定理

若正整数a,p满足(a,p)=1,则有ap11(modp)

  • proof:
    显然{0,1,2,,p1}p的一个完全剩余系。
    {0a,1a,2a,,(p1)a}也是p的一个完全剩余系。
    否则假设i,j{0,1,2,,p1},i>j,iaja(modp)那么(ij)a0(modp)(a,p)=1所以ij(modp)出现矛盾。
    因此有1×2××(p1)1a×2a××(p1)a1×2××(p1)×ap1(modp)ap11(modp)

但遗憾的是,费马小定理的逆命题并不成立,例如23401(mod341)341=11×31是个合数。

我们只能用费马小定理来判断一个数n是合数,若存在一个正整数a满足$a^{n-1}\neq 1 \left( \mod n \right) nan$的素性。

我们不总能通过增加参与测试的a的数量来提高正确率。因为存在一种数对bN,(b,n)=1,bn11(modn)这种数被称为卡迈克尔数,例如561=31117就是一个卡迈克尔数。(b,561)=1(b,3)=(b,11)=(b,17)=1b21(mod3)b101(mod11)b161(mod17)b560(b2)2801(mod3)b560(b10)561(mod11)b560(b16)351(mod17)b5601(mod561)

二次探测定理

p是一个素数,则x21(modp)的解只能是x1(modp)x1(modp)

  • proof:
    x21(modp)(x+1)(x1)0(modp)p|(x+1)(x1)p|x+1||p|x1

米勒检验

我们可以在费马素性测试的基础上利用二次探测定理进行进一步测试,如果bn11(modn)n为奇数,则检验bn121是否成立,若成立则认为n通过以b为基的米勒检验,若成立bn121则继续检验bn14

n是一个正整数,满足n>2,n1=2st,其中s是一个非负整数,t是一个奇正整数。称n通过以b为基的米勒检验,如果有bt1(modn)或者b2jt1(modn)对某个j成立,其中1js1

数学家们已经证明,若n是一个奇正整数,那么最多有n14b,其中1bn1,使得n能够通过以b为基的米勒检验。这说明如果随机选取k个正整数作为基,若n是一个合数,他通过所有k次米勒检验的概率仅为(14)k

可以验证,在264范围内,选取2,3,5,7,11,13,17,19,23,29,31,37作为基,可以确定性的验证一个正整数是否为素数。

Pollard-Rho分解

根据生日悖论,我们随机的选取$\left[ 1,N \right] Np\sqrt{p}p$意义下相等。

经验表明,在Pollard-Rho算法中选取,xk+1xk2+c(modn)作为随机数生成器有非常好的表现。

下面的图可以直观的感受到Rho是什么意思

Rho

这说明如果xixj(modp)则利用gcd(|xixj|,n)即可求出n的一个非平凡约数,如果不幸xi=xj那么会导致分解失败,然而事实上,由于pnp的环远小于模n的环,分解失败的概率极小,失败之后只需要更换随机数生成器的种子再次尝试分解。

那么如何找到这个环呢,记录出现过的数,并在里面查找是不够优秀的,因为还要算上查找的时间复杂度。我们可以引入两个变量x,yx每次走一步,y每次走两步,即xnew=x2+cynew=(y2+c)2+c每次检验gcd(|xy|,n),经过环长步之后x,y总能相遇。

但是每次都求gcd有点浪费时间,总的时间复杂度是O(n0.25log2n)这边有一个小技巧就是把连续lim步的|xy|相乘再做一次gcd,取lim=O(log2n)就可以优化掉一个log

另外Pollard-Rho往往在分解拥有大质因数的正整数时比较有效,在实际应用中可以先用试除法分解掉较小的质因子再用Pollard-Rho分解。

code:【模板】Pollard rho 算法

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>
#define int long long

using namespace std;

const int d[12] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

int prime[200005], a[1000005], n, cnt = 0, div1[1005][2];

int gcd(int o, int p) { return (!p) ? o : gcd(p, o % p); }

int mi(__int128 o, int p, int mo) {
    __int128 yu = 1;
    while (p) {
        if (p & 1) yu = yu * o % mo;
        o = o * o % mo;
        p >>= 1;
    }
    return yu;
}

bool test(int o, int p) {
    int yu = p - 1;
    if (mi(o, yu, p) != 1) return 0;
    while (!(yu & 1)) {
        yu >>= 1;
        if (mi(o, yu, p) == (p - 1)) return 1;
    }
    return mi(o, yu, p) == 1;
}

bool Miller_Rabin(int o) {
    if (o <= 2) return 1;
    for (int i = 1; i < 12; i++)
        if (o == d[i]) return 1;
    for (int i = 0; i < 12; i++)
        if (!test(d[i], o)) return 0;
    return 1;
}

int Pollard_Rho(int n) {
    int x1 = 1ll * rand() * rand() * rand() * rand() % n + 1, y = x1,
        c = rand() + 1;
    for (int lim = 1;; lim = min(lim << 1, 128ll)) {
        int cnt = 1;
        for (int i = 1; i <= lim; i++) {
            x1 = (__int128)x1 * x1 % n + c;
            y = (__int128)y * y % n + c;
            y = (__int128)y * y % n + c;
            cnt = (__int128)cnt * (x1 - y) % n;
        }
        int yu = gcd(cnt, n);
        if (yu > 1) return yu;
    }
    return n;
}

signed main() {
    srand((unsigned)time(NULL));
    for (int i = 1; i <= 100000; i++) a[i] = 0;
    for (int i = 2; i <= 100000; i++) {
        if (!a[i]) prime[++cnt] = i;
        for (int j = 1; (j <= cnt) && (prime[j] * i <= 100000); j++) {
            a[prime[j] * i] = 1;
            if (i % prime[j] == 0) break;
        }
    }
    int T;
    cin >> T;
    while (T--) {
        int n, s0 = 0;
        scanf("%lld", &n);
        if (Miller_Rabin(n)) {
            printf("Prime\n");
            continue;
        }
        for (int i = 1; i <= cnt; i++) {
            if (n % prime[i] == 0) {
                s0++;
                div1[s0][0] = prime[i];
                div1[s0][1] = 0;
                while (n % prime[i] == 0) {
                    n /= prime[i];
                    div1[s0][1]++;
                }
            }
        }
        while (!Miller_Rabin(n)) {
            int yu = Pollard_Rho(n);
            n /= yu;
            if (yu > n) swap(n, yu);
            if (yu > 1) {
                div1[++s0][0] = yu;
                div1[s0][1] = 1;
                while (n % yu == 0) div1[s0][1]++, n /= yu;
            }
        }
        if (n > 1) div1[++s0][0] = n, div1[s0][1] = 1;
        int ans = 0;
        for (int i = 1; i <= s0; i++) ans = max(ans, div1[i][0]);
        printf("%lld\n", ans);
    }
    return 0;
}
posted @   clapp  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示