6.1.5 质因数分解

质因数分解

根据唯一分解定理

任何一个大于 1 的整数都可以被分解成有限个质数的乘积的形式

\[n = \prod_{i = 1}^m p_i^{c_i}=p_1^{c_1} \times p_2^{c_2} \times \cdots \times p_m^{c_m} \]

我们有两种算法来分解一个数

试除法

直接枚举 \(2 \sim \sqrt n\) 的数,如果能整除,就一直除,直到把这个因子除完,时间复杂度 \(O(\sqrt n)\)

int p[N], c[N], cnt;
void divide (int n) {
    cnt = 0;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            p[++cnt] = i, c[cnt] = 0;
            while (n % i == 0) n /= i, c[cnt]++;
        }
    }
	if (n > 1) p[++cnt] = n, c[cnt]++;
}

\(Pollard-Rho\) 算法

这个神人算法让我们能够在 \(O(\sqrt p)\) 也就是 \(O(\sqrt[4] n)\) 的时间复杂度内求解出 \(n\) 的所有质因子,它和 \(Miller-Rabin\) 一样,都是随机算法,好消息是准确率同样高得离谱

我们先理解一下大概的思路,我们现在的目的是将 \(n\) 分解质因数

现在的问题是,我们没办法快速的找到 \(n\) 的某个质因数 \(p\)

所以 \(Pollard\) 发明了一种算法,利用数列(在 \(\bmod p\) 意义下)循环的性质,找到 \(n\) 的某个因数,然后递归求解,即可找到 \(N\) 的所有质因数

生日悖论

在讲具体算法之前,我们先说说这个反直觉的生日悖论。其实生日悖论不是一个逻辑悖论,只是很反直觉罢了

一个房间里有 23 个人,他们中两人生日相同的概率超过一半(不考虑闰年,他们的生日完全随机)

证明:正难则反,生日相同的概率不好统计,但生日不同的概率较好统计,设 \(A\) 为这 23 个人生日都互不相同

\[P(A) = \frac {364}{365} \times \frac {363} {365} \times \cdots \times \frac {365-22} {365} < \frac 1 2 \]

如何理解?我们考虑第前两个人生日不在同一天的概率,自然就是除去第一个人的生日,第二个人在剩下的日子里随便选,后面的也一样

生日悖论能告诉我们,如果我们不断在某个范围内生成随机整数,那么很快就会出现重复的数,期望大概是根号级别的。更准确的说,对于一个 \([1,N]\) 范围内整数的理想随机数生成器,生成序列中第一个重复数字前,期望有 \(\sqrt {\frac {\pi N} 2}\) 个数

应用到题目上,即对于最坏情况 \(n = p^2\) 如果我们不断在 \(1 \sim n - 1\) 之间生成随机数,那么期望在生成 \(\sqrt p = \sqrt[4] n\) 个数后出现在 \(\bmod p\) 意义下相同的数,那么这两个数的差的绝对值 \(d\) 就满足 \(d \equiv 0 \ (\bmod p)\) ,于是也满足 \(\gcd(d, n) > 1\)

但这个东西并不能直接用,因为它是在 两两比较 的情况下才成立的,如果我们枚举两个数进行比较,那么时间复杂度退化到 \(O(\sqrt n \log n)\) ,甚至不如试除法

伪随机数列

\(Pollard\) 代佬想出了一种很神奇的伪随机数生成器来生成 \([0, N - 1]\) 间的伪随机数序列,设序列第一个数为 \(x\)\(f(x) = (x^2 + c) \bmod N\) ,则 \(x,f(x),f(f(x)),\cdots\) 为一个伪随机数序列

这个序列有个简单性质,就是后一项的值是根据前一项的值推导过来的,那么一旦出现重复的数,就会进入循环,如下图(\(c = 1\)

image-20250705192505468

这里我们假定这个伪随机数列足够随机,那么期望时间复杂度就是 \(O(\sqrt p)\)

那么这个序列怎么就能够降低时间复杂度了呢?

它还有个非常重要的性质

如果 \(|i - j| \equiv 0 \ (\bmod p)\) ,那么一定有 \(|f(i) - f(j)| = |i^2 - j^2| = |i - j| \cdot |i + j| \equiv 0 \ (\bmod p)\)

由此可得,只要距离为 \(d\) 的两个数满足条件,那么所有距离为 \(d\) 的数均满足条件,那么我们每算一个 \(d\) 其实相当于算了这个序列上所有这个距离的数的差值,所以我们每个距离只需要算一次即可,下一次就去算下一个距离

如果伪随机数序列足够随机,那么期望的时间复杂度为\(O(\sqrt[4]n \log n)\)

 inline ll Pollard_rho (ll n) {
        static mt19937_64 eg ((unsigned int)time (0));
        uniform_int_distribution <ll> u0 (1, n - 1);
        ll c = u0 (eg);
        auto f = [&](ll t) { return ((i8)t * t + c) % n; };
        ll x = f(0), y = f(x);
        while (x != y) {
            ll d = gcd (abs (x - y), n);
            if (d != 1) return d;
            x = f(x), y = f(f(y));
        }
        return n;
    }

但实际上还可以进一步优化,如果 \(\gcd(d, n) > 1\) 那么 \(\gcd (kd \bmod n, n) > 1\) ,所以我们可以减少 \(\gcd\) 的次数,即先将待选数乘起来,再统一求 \(\gcd\)

我们可以倍增去求,每隔 \(1,2,4,8,\cdots\) 求一次 \(\gcd\) ,真阳只需要求期望 \(\log(\sqrt[4] n)\) 次公因数,总期望时间复杂度为 \(O(\sqrt[4] n + \log(\sqrt[4] n)\log n)\) ,可以看做 \(O(\sqrt[4] n)\)

但这样有个缺点,后面间隔很大,可能已经满足条件却迟迟无法退出,所以我们固定一个间隔,每隔这么多次就求一次 \(\gcd\)

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <ctime>
#include <random>

using namespace std;

namespace michaele {

    typedef long long ll;
    typedef __int128 i8;

    int pri[] = {2, 3, 5, 7, 13, 29, 37};
    vector <ll> fac;

   
    inline ll pw (ll a, ll b, ll mod) {
        ll res = 1, t = a;
        while (b) {
            if (b & 1) {
                res = (i8)res * t % mod;
            }
            t = (i8)t * t % mod;
            b >>= 1;
        }
        return res;
    }

    inline ll gcd (ll a, ll b) {
        if (a == 0 || b == 0) return a | b;
        if (a == b) return a;
        int az = __builtin_ctzll (a);
        int bz = __builtin_ctzll (b);
        int z = min (az, bz);
        ll diff;
        b >>= bz;
        while (a) {
            a >>= az;
            diff = a - b;
            if (!diff) break;
            az = __builtin_ctzll (diff);
            b = a < b ? a : b;
            a = diff < 0 ? -diff : diff;
        }
        return b << z;
    }
    
    inline bool MR (ll n) {
        if (n < 3) return n == 2;
        if (!(n & 1)) return false;
        ll d = n - 1, k = 0;
        while (!(d & 1)) d >>= 1, k++;
        for (int i = 0; i < 7; i++) {
            ll v = pw (pri[i], d, n);
            if (v <= 1 || v == n - 1) continue;
            for (int j = 1; j <= k; j++) {
                v = (i8)v * v % n;
                if (v == n - 1 && j != k) {
                    v = 1;
                    break;
                }
                if (v == 1) return false;
            }
            if (v != 1) return false;
        }
        return true;
    }
    
    inline ll Pollard_rho (ll n) {
        static mt19937_64 eg ((unsigned int)time (0));
        uniform_int_distribution <ll> u0 (1, n - 1);
        ll c = u0 (eg);
        auto f = [&](ll t) { return ((i8)t * t + c) % n; };
        ll x = 0, y = 0, s = 1, k = 1;

        while (1) {
            for (int i = 1; i <= k; i++) {
                x = f(x);
                s = (i8)s * abs (x - y) % n;
                if ((i & 127) == 0) {
                    ll d = gcd (s, n);
                    if (d > 1) return d;
                }
            }
            ll d = gcd (s, n);
            if (d > 1) return d;
            
            y = x;
            s = 1;
            k <<= 1;
        }
        while (x != y) {
            ll d = gcd (abs (x - y), n);
            if (d != 1) return d;
            x = f(x), y = f(f(y));
        }
        return n;
    }
    
    ll mafac;
    void get_fac (ll n) {
        if (n == 1) return;
        if (n == 4) {
            fac.push_back (2);
            fac.push_back (2);
            return;
        }
        if (MR (n)) {
            mafac = max (mafac, n);
            return;
        }
        ll tmp = n;
        while (tmp == n) tmp = Pollard_rho (n);
        get_fac (tmp), get_fac (n / tmp);
    }

    inline void rd (ll &res) {
        res = 0; char c = getchar ();
        while (c < '0' || c > '9') c = getchar ();
        while (c >= '0' && c <= '9') {
            res = (res << 1) + (res << 3) + (c ^ 48);
            c = getchar ();
        }
    }
    void main () {
        ios :: sync_with_stdio(0);
        cin.tie(0);
        cout.tie (0);
        ll T, x;
        rd(T);
        while (T--) {
            fac.clear ();
            rd(x);
            if (MR (x)) {
                cout << "Prime" << "\n";
                continue;
            }
            mafac = 0;
            get_fac (x);
            cout << mafac << "\n";
        }
    }
}
int main () {
    michaele :: main ();
    return 0;
}
posted @ 2025-08-11 10:22  michaele  阅读(16)  评论(0)    收藏  举报