1 条题解

  • 0
    @ 2024-7-26 8:21:03

    这玩意我其实搞了很久。

    Upd on 2024.7.26:这篇文章是我在 2023.4.29 编写的,而我从 2023.2.1 就开始搞分解,一直到现在,详见 该专栏

    Miller-Rabin && Pollard ρ\rho

    这篇文章

    Pollard P-1 算法

    设要分解的数为 nn

    如果 n1n-1 的所有素因子都是小于等于 BB 的,则直接计算 gcd(2B!1,n)\gcd(2^{B!}-1,n) 就可以求出 nn 的一个素因子。

    原理:将费马小定理改成同余形式:at(p1)1=kpa^{t(p-1)}-1=kp。也就是说,如果指数 expexpp1p-1 的倍数,那么 paexp1p|a^{exp}-1

    对于合数 n=pqn=pq,如果我们能找到一个指数 BB,使得对于某一个底数 aa,有 paB1p\mid a^B-1qaB1q\nmid a^B-1,那么计算 gcd(aB1,n)\gcd(a^B-1,n) 就分解了 nn

    这里 BB 就是 pp 的最大素因子。也即如果 ppBsmoothB-\text{smooth} 的且 qq 不是,那么就可以选择这样的 BB。反之,找到的就是 qq

    复杂度证明:不会。

    __uint128_t pollard_pm1(__uint128_t value) {
        if (__uint128_t ret = sqrtl(1.0 * value) + 1e-6; ret * ret == value) return ret;
        mint::set_mod(value, false);
        mint a(2);
        for (uint64_t j = 2;; j++) {
            a = a.pow(j);
            __uint128_t ret = std::__gcd((a - 1).val(), value);
            if (ret > 1) return ret;
        }
    }
    

    SQUFOF

    即 Shanks 的平方型分解算法。当要分解的数的两个素因数比较近时这个算法跑得很快。

    不知道如果两个素因子差为 mm,那么时间复杂度是否为 O(m)O(\sqrt{m})。反正这个算法运算量有一个精确上界 32n3\sqrt{2\sqrt{n}}(还要再乘以一个常数,视实现决定)。

    __uint128_t squfof(__uint128_t value) {
        __uint128_t s = sqrtl(1.0l * value) + 1e-6;
        while (s * s > value) s--;
        if (s * s == value) return s;
        __int128 d, po, p, p_prev, q, q_prev, q_, b, r;
        long long l, b_, i;
        int k = 0;
        l = 2 * sqrtl(2.0l * s);
        b_ = 3 * l;
        while (++k) {
            d = k * value;
            po = p_prev = p = sqrtl(1.0l * d);
            q_prev = 1;
            q = d - po * po;
            while (q < 0) {
                po--;
                p_prev--;
                p--;
                q = d - po * po;
            }
            if (!q) return gcd(value, k);
            for (i = 2; i < b_; i++) {
                b = (po + p) / q;
                p = b * q - p;
                q_ = q;
                q = q_prev + b * (p_prev - p);
                r = sqrtl(1.0 * q) + 1e-6;
                if (!(i & 1) && r * r == q) break;
                q_prev = q_;
                p_prev = p;
            }
            if (i >= b_) continue;
            b = (po - p) / r;
            p_prev = p = b * r + p;
            q_prev = r;
            q = (d - p_prev * p_prev) / q_prev;
            i = 0;
            do {
                b = (po + p) / q;
                p_prev = p;
                p = b * q - p;
                q_ = q;
                q = q_prev + b * (p_prev - p);
                q_prev = q_;
                i++;
            } while (p != p_prev && i < b_);
            if (i >= b_) continue;
            r = gcd(value, q_prev);
            if (r != 1) return r;
        }
        return 0;
    }
    

    Dixon 的随机平方算法

    如果我们手上有一系列(不同的)x2y2(modn)x^2\equiv y^2\pmod n 的式子,那么每一个都有 50%50\% 的概率分解 nn

    随机一个 [1,n]\lbrack 1,n\rbrackrr,令

    g(r)r2(modn)g(r)\equiv r^2\pmod n

    然后用 BB 内的素数 g(r)g(r)。如果 g(r)g(r) 不是 BsmoothB-\text{smooth} 的,那么就重新选一个 rr

    对于每一个 rr 可以最终得到这样的式子:

    $$\begin{array}{l} z&exp_{B_1}&exp_{B_2}&\cdots&exp_{B_{|B|}} \end{array} $$

    其中 expBiexp_{B_i}g(r)g(r) 除以 BiB_i 的余数(mod2\bmod 2)。

    将所有的这些关系组合,就可以得到一个 010-1 矩阵。对这个矩阵进行高斯消元。即选出一些关系,使它们异或起来为

    $$\begin{array}{l} \prod\limits_{r\in A}r&0&0&\cdots&0 \end{array} $$

    通过这些式子就可以分解 nn

    例:分解 998244359987710471998244359987710471

    选取 B={2,3,5,7,11,13}B=\{2,3,5,7,11,13\}

    矩阵如下:

    $$\begin{array}{l} &2&3&5&7&11&13\\ 895200474254967936&0&1&0&1&1&0\\ 470344441344285771&1&0&1&0&0&1\\ 741511338075501596&1&1&1&1&1&1\\ \end{array} $$

    将这些关系的左右乘起来得到 2586417298104620662300302258641729810462066^2\equiv 30030^2。计算 gcd(258641729810462066+30030,998244359987710471)\gcd(258641729810462066+30030,998244359987710471) 得到 998244359987710471998244359987710471 的一个非平凡因子 10000000071000000007

    二次筛

    前置知识:二次剩余

    上面这个算法的问题在于如何正确选取 rr 和如何构造 g(r)g(r)。幸运的是,我们可以取 $g(r)=(r+\lfloor x\rfloor)^2-x\sim 2r\lfloor x\rfloor$。

    通过实践可以发现,g(r)g(r)BsmoothB-\text{smooth} 的。

    然后就是要在模意义下求解 pg(x)p\mid g(x),即 x2a(modp)x^2\equiv a\pmod p。这可以使用 Cipolla 算法。

    这就是单多项式二次筛(SPQS,Simple Polynomial Quadratic Sieve)。

    优化们:

    1. 模数只取 pmod4=3p\bmod 4=3 的情况。这样可以避免使用 Cipolla 算法。
    2. 我们不仅可以选用正值的 rr,也可以选用负值的 rr,只需选取偶数个负值的 rr 符号即可抵消,所以我们在矩阵中使用一列记录是否为负即可。
    3. 可能会有部分数不 BsmoothB-\text{smooth},但是分解后剩余的数在 (BB,BB2)(B_{|B|},B_{|B|}^2),这必然剩的是一个大质数 pp。对于这些数,我们并不能直接在表格中加入 pp 列,但是如果有相同 pp 的多个数 {r1,r2,,rm}\{r_1,r_2,\cdots,r_m\},我们就可以将 r1r2,r1r3,,r1rmr_1r_2,r_1r_3,\cdots,r_1r_m 加入,这样 pp 就被平方了。
    4. 使用多个多项式。

    接下来重点讲第 4 个优化。设 g(r)=ar2+2br+cg(r)=ar^2+2br+c,则 ag(r)=a2r2+2abr+acag(r)=a^2r^2+2abr+ac,若 b2ac=nb^2-ac=n,我们就可以有 ag(r)=(ar+b)2nag(r)=(ar+b)^2-n,则 (ar+b)2ag(r)(modn)(ar+b)^2\equiv ag(r)\pmod n。若 aa 为平方数,我们就可以考虑筛这些新的 gg。我们可以取小质数 pp,令 a=p2,b2n(modp)a=p^2,b^2\equiv n\pmod p 并解出 cc

    这就是重多项式二次筛(MPQS,Multiple Polynomial Quadratic Sieve)。

    时间复杂度 exp((1+o(1))lnnlnlnn)\exp(\sqrt{(1+o(1))\ln n\ln\ln n})


    一份集成实现见 提交记录 #1720023 - LibreOJ。(没有集成 ECM)

    参考资料:

    1. 整数分解
    2. SQUFOF
    3. 二次筛
    • 1

    信息

    ID
    2119
    时间
    3000~4000ms
    内存
    512MiB
    难度
    10
    标签
    递交数
    4
    已通过
    1
    上传者