1 条题解
-
0Guest MOD
-
0
这玩意我其实搞了很久。
Upd on 2024.7.26:这篇文章是我在 2023.4.29 编写的,而我从 2023.2.1 就开始搞分解,一直到现在,详见 该专栏。
Miller-Rabin && Pollard
见 这篇文章。
Pollard P-1 算法
设要分解的数为 。
如果 的所有素因子都是小于等于 的,则直接计算 就可以求出 的一个素因子。
原理:将费马小定理改成同余形式:。也就是说,如果指数 是 的倍数,那么 。
对于合数 ,如果我们能找到一个指数 ,使得对于某一个底数 ,有 且 ,那么计算 就分解了 。
这里 就是 的最大素因子。也即如果 是 的且 不是,那么就可以选择这样的 。反之,找到的就是 。
复杂度证明:不会。
__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 的平方型分解算法。当要分解的数的两个素因数比较近时这个算法跑得很快。
不知道如果两个素因子差为 ,那么时间复杂度是否为 。反正这个算法运算量有一个精确上界 (还要再乘以一个常数,视实现决定)。
__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 的随机平方算法
如果我们手上有一系列(不同的) 的式子,那么每一个都有 的概率分解 。
随机一个 的 ,令
然后用 内的素数 。如果 不是 的,那么就重新选一个 。
对于每一个 可以最终得到这样的式子:
$$\begin{array}{l} z&exp_{B_1}&exp_{B_2}&\cdots&exp_{B_{|B|}} \end{array} $$其中 是 除以 的余数()。
将所有的这些关系组合,就可以得到一个 矩阵。对这个矩阵进行高斯消元。即选出一些关系,使它们异或起来为
$$\begin{array}{l} \prod\limits_{r\in A}r&0&0&\cdots&0 \end{array} $$通过这些式子就可以分解 。
例:分解 。
选取 。
矩阵如下:
$$\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} $$将这些关系的左右乘起来得到 。计算 得到 的一个非平凡因子 。
二次筛
前置知识:二次剩余
上面这个算法的问题在于如何正确选取 和如何构造 。幸运的是,我们可以取 $g(r)=(r+\lfloor x\rfloor)^2-x\sim 2r\lfloor x\rfloor$。
通过实践可以发现, 是 的。
然后就是要在模意义下求解 ,即 。这可以使用 Cipolla 算法。
这就是单多项式二次筛(SPQS,Simple Polynomial Quadratic Sieve)。
优化们:
- 模数只取 的情况。这样可以避免使用 Cipolla 算法。
- 我们不仅可以选用正值的 ,也可以选用负值的 ,只需选取偶数个负值的 符号即可抵消,所以我们在矩阵中使用一列记录是否为负即可。
- 可能会有部分数不 ,但是分解后剩余的数在 ,这必然剩的是一个大质数 。对于这些数,我们并不能直接在表格中加入 列,但是如果有相同 的多个数 ,我们就可以将 加入,这样 就被平方了。
- 使用多个多项式。
接下来重点讲第 4 个优化。设 ,则 ,若 ,我们就可以有 ,则 。若 为平方数,我们就可以考虑筛这些新的 。我们可以取小质数 ,令 并解出 。
这就是重多项式二次筛(MPQS,Multiple Polynomial Quadratic Sieve)。
时间复杂度 。
一份集成实现见 提交记录 #1720023 - LibreOJ。(没有集成 ECM)
参考资料:
- 1
信息
- ID
- 2119
- 时间
- 3000~4000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 4
- 已通过
- 1
- 上传者