Miller-Rabin 与 Pollard-Rho
naoliaok_lovely · · 算法·理论
Miller-Rabin 素性测试
就像他的名字,这个算法是由 Miller 和 Rabin 二人所提出,用于判断一个数是不是质数。
在此之前,我们使用的都是复杂度为
事实上,素数判定为 P 问题,目前已知的确定性素数判断方法为 APRCL,复杂度
既然如此,我们的 miller-rabin 算法便选择了“随机”的策略。一般在工业中还有一种非确定性算法叫 ECCP,不过他是绝对正确的,“非确定”指的是因为随机导致的程序运行效率不同,为
费马测试
在介绍 miller-rabin 算法之前,先让我们看看费马测试。我们都知道,对于质数
这是一个很不错的想法,但很遗憾他是错误的……不过幸运的是,在大多数情况下,这个方法都是正确的。
于是我们可以尝试进行改进——既然找一个底数不行,那多找几个呢?可惜的是有一类数,他们是合数,却也满足上面费马小定理的性质!这些数被称为 Carmichael 数,不过分布十分稀疏,在
性质:Carmichael 数至少有
二次探测定理
如果
考虑一个问题:什么样的合数可以通过二次探测?
(易证)
结论:当且仅当
改进
Miller-Rabin 就是将二者结合了一下。事实上, Carmichael 数有一个性质:其一定不是
- 将
p 分解为2^km ,即找出当中的偶数质因子。 - 对于底数
a ,计算a^m\bmod p ,然后不断平方。由二次探测定理得,设当前的余数为r ,且r^2\equiv1\pmod p ,则r\equiv\pm1\pmod p 。如果出现矛盾则不是质数。 - 如果最终的余数不为
1 则不是质数。
多次随机底数
时间复杂度:
在 OI 中,miller-rabin 常常用来计算 long long 范围内的数,所以乘法和取模均可看做
优化
上面的随机看着就不靠谱。事实上,对于 int 范围内的数,我们只需要验证
bool miller_rabin(LL n)
{
if(n <= 1 || n > 2 && ~n & 1) return false;
LL m = n - 1, k = 0;
while(~m & 1) m >>= 1, k++;
vector<int> s = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
for(int a : s)
{
if(a >= n) break;
i128 x = ksm(a, m, n), y;
for(int i = 1; i <= k; i++, x = y)
{
y = x * x % n;
if(y == 1 && x != 1 && x != n - 1) return false;
}
if(x != 1) return false;
}
return true;
}
Rollard-Rho
P4718 【模板】Pollard-Rho
可以在
质因数分解本身为 NP 问题、反 NP 问题、BQP 问题。
生日悖论
先引入生日悖论,不过相信很多人都知道,即:
在一个班的同学中,存在两个人生日相同的概率是多少?
相信大家都很熟悉,这里就不证明了,总之一句话就是,看起来概率很小,实则概率接近
随机与 \gcd
同样考虑我们以前的分解方法。最优的就是先枚举
但是,随机一个数简直太荒谬了,可以说运气不好的话跑几分钟甚至几十分钟绰绰有余。对于我们随机到的数字
我们可以采用求
随机函数
Pollard-Rho 最精妙的一点就在于它的随机函数。首先,我们随机
举个例子,设
这也是其名字
生成函数的性质:若
所以我们只需判断下标之差不同的数对。
于是,对于下标的差不同的两项,我们取他们的差,在执行
floyd 判环
先让我们根据上面的理论,总结一下实现步骤:
- 如果
n 不为合数,直接返回。 - 定义上述的递推函数(可以用下面的 Lambda 表达式写法,看起来清新一点)。
- 随机
d,x_1 ,然后进行循环,每次求出下标差不同的两项的差。如果\gcd>1 ,那么递归分解。
可惜这个方法并不完美,存在两个很大的缺陷。其一是如何寻找下标差不同的数对,这个先放着,重点在第二个。仔细思考上面的
这个情况显然是会出现的,并且很容易构造。考虑修正,一种比较直观的方法是,一旦发现出现了环,那么就重新随机一个
这里引入 floyd 判环的思想,可以同时规避以上两个问题。简单点说就是在“龟兔赛跑”。我们设置两个变量
- 随机
d,x ,令y=(x^2+d)\bmod n 。重复执行,如果\gcd(|x-y|,n)>1 则递归分解,否则x 跳一步,y 跳两步。 - 如果
x=y ,重新随机d,x 。
优化
仔细思考,如果对于每次我们即将判断的数都取一遍
在题解中有一个倍增优化的东西,是在对这个阈值进行倍增处理,即可优化掉这个
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define i128 __int128
mt19937_64 rd(time(0));
LL n;
i128 ksm(i128 x, i128 y, LL mod)
{
i128 res = 1;
x %= mod;
while(y)
{
if(y & 1) res = res * x % mod;
y >>= 1;
x = x * x % mod;
}
return res;
}
bool miller_rabin(LL n)
{
if(n <= 1 || n > 2 && ~n & 1) return false;
LL m = n - 1, k = 0;
while(~m & 1) m >>= 1, k++;
vector<int> s = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
for(int a : s)
{
if(a >= n) break;
i128 x = ksm(a, m, n), y;
for(int i = 1; i <= k; i++, x = y)
{
y = x * x % n;
if(y == 1 && x != 1 && x != n - 1) return false;
}
if(x != 1) return false;
}
return true;
}
vector<LL> p;
void pollard_rho(LL n)
{
if(n <= 1) return;
if(miller_rabin(n))
{
p.push_back(n);
return;
}
LL d = rd() % (n - 1) + 1;
auto f = [&](LL x)
{
return ((i128)x * x + d) % n;
};
LL x = rd() % (n - 1) + 1, y = f(x), t = 1;
while(t == 1)
{
for(int i = 1; i <= 32; i++)
{
while(x == y)
d = rd() % (n - 1) + 1, x = rd() % (n - 1) + 1, y = f(x);
if((i128)t * abs(x - y) % n == 0) break;
t = (i128)t * abs(x - y) % n;
x = f(x), y = f(f(y));
}
t = __gcd(n, t);
}
pollard_rho(t), pollard_rho(n / t);
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n;
if(miller_rabin(n))
{
puts("Prime");
continue;
}
p.clear();
pollard_rho(n);
LL ans = 0;
for(LL i : p) ans = max(ans, i);
cout << ans << endl;
}
return 0;
}