其实这里的整数在整数分解领域并不算太大,之前并没有学习过这类的算法,正好也算是补上了。在这里我使用了 Pollard Rho 算法,其他的算法还有 Fermat Rho 和 Quadratic Sieve 算法。
Pollard Rho#
核心思想#
假设要分解的整数 n=pq,其中 p,q 都是质数,不妨设 p≤q。我们使用 g(x)=(x2+1) (mod n) 来生成一个伪随机数序列。
不妨设初始的 x 为 2,那么我们有一个序列为 {x1=g(2),x2=g(g(2)),...xk=gk(2),...},因为序列中的值一定是有穷的,并且序列的每一个数只依赖于前一个数,所以该序列一定会循环。
定义 {yk=xk (mod p),k=1,2,...} 序列,那么这个序列也一定会循环,并且由于 p 比 n 小得多,所以序列循环也一定会比 {xk} 序列循环得早许多。
那么我们只需要检测到这个环,假设环长为 r,他一定有yr+k−yk≡0 (mod p),所以使用 Floyd Cycle Detection ,并使用最小公约数来检测环的出现 (最小公约数不为 1),此时如果最小公约数为不等于 n,那么它一定是 p 或者 q。
唯一需要注意的是环出现的时候可能最小公约数为 n,也就是上述算法中 x 等于 y。此时我们随机更换序列种子 x,并进行下一轮分解。
时间复杂度#
期望运行时间正比于 n 的最小素数的开根,此处大约为 O(3.2e4)。
#188 整体思路#
定义 T(a,b)=a↑↑b。
我们有 T(a,b)=aT(a,b−1)。
由欧拉定理的扩展,如果 T(a,b−1)>=ϕ(m),则有 T(a,b)≡aT(a,b−1) % ϕ(m)+ϕ(m) (mod m),否则有 T(a,b)≡aT(a,b−1) % ϕ(m) (mod m),此时需要求解 T(a,b−1) (mod ϕ(m))。
同理,T(a,b−1)≡aT(a,b−2) % ϕ(ϕ(m)) ?+ ϕ(ϕ(m)) (mod ϕ(m)),此时求解 T(a,b−2) (mod ϕ(ϕ(m))),我们可以一直递归下去。
直到求解 T(a,1) (mod ϕb−1(m)),或者 T(a,b′) (mod 1),ϕb−b′(m)=1 为止。
递归深度#
递归深度顶多为 min(128, b - 1),证明如下:
递归结束条件有两个,一个是 b 等于 1,或者欧拉函数为 1。
我们证明 m≥2 时,ϕ(ϕ(m))≤m/2 即可,由 ϕ(m) 定义, 1≤ϕ(m)≤m 恒成立。
分以下两种情况:
- m 为偶数,则由定义 ϕ(m)≤m/2,ϕ(ϕ(m))≤ϕ(m)≤m/2
- m 为奇数,则由定义ϕ(m)一定为偶数,ϕ(ϕ(m))≤ϕ(m)/2≤m/2
证毕。
所以如果 ϕs(m)=1,m<264,那么 s≤64×2=128 恒成立,所以递归深度最大不会超过 128。
扩展欧拉定理的应用#
扩展形式由于有一个大小比较的条件,所以需要估算当前栈迭代次幂的大小,而迭代次幂 (tetration) 增长十分快,所以值在 (2^63 - 1) 以内的 a 和 b 甚至可以枚举出来:
| a | b | T(a, b) |
|---|
| 1 | any | 1 |
| any | 1 | a |
| 2 | 2 | 4 |
| 2 | 3 | 16 |
| 2 | 4 | 65536 |
| 3 | 2 | 27 |
| 3 | 3 | 7625597484987 |
| 4 | 2 | 64 |
| … | … | … |
| 15 | 2 | 437893890380859375 |
这些数我们可以轻易的计算出来,与模数进行比较,而其余的一定大于模数。
大整数分解#
由于 m 的范围,我先打表 1e6 内所有的素数,并先将 m 中这样的质因子全部分解,假设剩余的数为 m′,那么 m′ 如果是合数,一定是两个质数 p,q>1000000 的乘积。
如果 m′≤1e12,那么 m′ 一定是素数,否则我们先对 m′ 进行素数测试 (Miller-Rabin),如果为合数再使用 Polland-Rho 进行分解,一旦分解一定为两个素数。
64 位模乘 trick#
Gcc/Clang 均提供内置类型 __int128_t,可以支持 128 位的运算,而如果运行在 intel x86_64 架构上,可以直接使用如下汇编
long mulmod(long a, long b, long m) {
asm("mulq %2; divq %3" : "=d"(res), "+a"(a) : "S"(b), "c"(m));
算法流程#
T(a,b,m):
- 如果 a == 1 或者 b == 1,返回 a % m
- 如果 m == 1,返回 0
- 对 m 进行质因数分解,并计算欧拉函数 ϕ(m)
- 递归计算 e=T(a,b−1,ϕ(m))
- 估算 T(a,b−1) 并与 ϕ(m) 比较大小,如果 T(a,b−1)≥ϕ(m),e=e+ϕ(m)
- 计算 ae%m
整个流程保证了所有操作数都在 64 位以内,递归深度最大为 min(128, b - 1)。
算法代码托管在 https://github.com/arkbriar/hackerrank-projecteuler/blob/master/cpp/188.cc 。