Miller-Rabin & Pollard-rho
Miller–Rabin & Pollard-rho
Miller–Rabin
质数判断。
考虑费马小定理:
其逆定理几乎也成立。那么随机选择一些底数
这是错的……正确率暂且不谈。有一些合数
看来不太行。考虑一个东西叫做二次探测定理。
对于质数
怎么证?首先移项并因式分解为
对于合数就不一定了。比如,
那么,每次随机一个数字
诶,你会发现,这个还是错完了。这种数字的反例比 Carmichael 数还要密集得多。光是
但是你会发现,这种数字和 Carmichael 数……好像没有交集?
还真是。我们先写出判断证据(即能够证明
等一下,我们还没有榨干
然后想一想怎么写更简单。我们可以对
Miller-Rabin 就可以这么写。
然后我们再证明它的正确性。
定理:如果
而显然如果
所以,只要
理性一点?
如果我们要变成确定性算法,那自然是控制随机数的选择了。实际上,对于
什么,你记不住
要注意的一点是,在
代码
注意一个事,如果 long long 范围的那么如果乘法不使用龟速乘那么会爆炸。所以可以使用 __int128(当然用龟速乘也可以)。
:::info[Miller-Rabin]
__int128 qpow(__int128 x, __int128 y, __int128 p); // 快速幂怎么实现不用我说了吧
constexpr unsigned basis[15] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
bool is_probab_prime(long long n)
{
unsigned k = 0, m = n - 1;
while(m % 2 == 0)
{
k++;
m /= 2;
}
auto chkone = [&](long long x)
{
if(x==n) return true;
__int128 g = qpow(x, m);
if(g == 1) return true;
for(int i=1;i<=k;i++)
{
if(g == n - 1) return true;
g = g * g % n;
}
return false;
};
for(int i=1;i<=12;i++)
{
if(!chkone(basis[i])) return false;
}
return true;
}
:::
代码是十分简洁的。
时间复杂度
显然是
Pollard-rho
Pollard-rho 和 Miller–Rabin 一样,充满人类智慧。
Pollard-rho 用于求出一个合数
Pollard-rho 的期望时间复杂度为
Pollard-rho 非常非常玄学。它既不能保证分解成功,也不能保证运行时间。尽管你可以多次尝试分解,但是一次分解失败时间复杂度就是
首先,我们有一种玄学的求解方式。多次随机一个数字
这个东西假完了,设其最小质因子为
什么情况下会引入根号?好吧,其实挺多的。但是 Pollard-rho 的核心原理是生日悖论。
生日悖论:不停随机
如果我们可以构建一个模
额,其实我们不知道
如果两两作差求和
通常采用的迭代方式是
也就是说,相同距离的数对,只需要检查一个!
考虑 Floyd 判环。两个人分别从起点开始,一个人一次移动
那么考虑最小因子
解决了!
等一下如果分解失败那不就是
优化
两个优化。
Brent 判环:在第
看起来假完了,但实际上是对的。这样的移动次数甚至永远不大于 Floyd 判环。平均情况下能减少
“分块”
代码
使用 Brent 判环和倍增优化。
:::info[代码]
#include <cstdio>
#ifndef ONLINE_JUDGE // 这份代码中所有位于 #ifndef ONLINE_JUDGE 和 #endif 之间的部分都可以不管,是用于本地调试的。
#include <__msvc_int128.hpp> // 本地不支持 __int128,刚好 MSVC 有 __msvc_int128.hpp,定义了 _Signed128,可以使用。
#define __int128 _Signed128
#endif
using namespace std;
__int128 qpow(__int128 x, __int128 y, __int128 mod)
{
__int128 p = x % mod, ans = 1;
do
{
if (y & 1) ans = ans * p % mod;
p = p * p % mod;
} while (y >>= 1);
return ans;
}
unsigned basis[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
bool is_probab_prime(__int128 n)
{
long long k = 0;
__int128 m = n - 1;
while (1 ^ m & 1) // 什么花式写法,但是其实是我不知道 ^ 和 & 的优先级哪个高,但是你发现不管哪个更高结果都是对的。
{
k++;
m >>= 1;
}
auto chkone = [&](__int128 p)
{
if (n == p) return true;
p = qpow(p, m, n);
if (p == 1) return true;
for (int i = 1; i <= k; i++)
{
if (p == n - 1) return true;
p = p * p % n;
}
return false;
};
for (unsigned x : basis)
{
if (!chkone(x)) return false;
}
return true;
}
__int128 gcd(__int128 x, __int128 y) { return y == 0 ? x : gcd(y, x % y); }
unsigned pool[] = { 956008337,1070185313,1097705111,1083875081,930518497,948253391,1023815263,905918851,969311227,1074002201,934286407,1009093507,981354329,1040723269,1076316677,1090025219 }; // 使用 Python 生成随机质数.jpg
unsigned primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
long long pollard_rho_inner(long long n, __int128 c)
{
for (int x : primes)
{
if (n % x == 0) return x;
}
__int128 alpha = 1, beta = 1;
int ctt = 0;
constexpr unsigned big_banana = 31; // 神金 B.B.
for (int i = 1;; i <<= 1)
{
__int128 mul = 1;
for (int j = 1; j <= i; j++)
{
beta = (beta * beta % n + c) % n;
auto dd = beta > alpha ? beta - alpha : alpha - beta;
mul = mul * dd % n;
if (alpha == beta) return n;
if (++ctt == big_banana)
{
ctt = 0;
auto res = gcd(mul, n);
if (res > 1) return (long long)res;
mul = 1;
}
}
auto res = gcd(mul, n);
if (res > 1) return (long long)res;
alpha = beta;
}
}
long long pollard_rho(long long n)
{
for (auto x : pool)
{
auto res = pollard_rho_inner(n, x);
if (res != n) return res;
}
return -1;
}
int main()
{
#ifndef ONLINE_JUDGE // 测试是否都能成功分解。
for (int i = 2; i <= 10000000; i++)
{
if (is_probab_prime(i)) continue;
long long res = pollard_rho(i);
if (res == i || res == -1)
{
printf("%d\n", i);
}
}
return 0;
#endif
int t;
scanf("%d", &t);
while (t--)
{
long long n;
scanf("%lld", &n);
if (is_probab_prime(n)) printf("Prime\n");
else
{
auto calc = [&](auto self, long long n) -> long long
{
if (n <= 1 || is_probab_prime(n)) return n;
long long g = pollard_rho(n);
while (n % g == 0) n /= g;
long long r = self(self, g);
while (n % r == 0) n /= r;
long long p = self(self, n);
return r > p ? r : p;
};
printf("%lld\n", calc(calc, n));
}
}
return 0;
}
:::
:::info[彩蛋]
#include<bits/stdc++.h>
using namespace std;
char p[1024];
int main()
{
int t;
cin >> t;
stringstream w;
for (int i = 1; i <= t; i++)
{
long long n;
cin >> n;
w << "factor " << n << " ; ";
}
FILE* fp = popen(w.str().c_str(), "r");
for (int i = 1; i <= t; i++)
{
fgets(p, 1024, fp);
int le = strlen(p);
p[le++] = ' ';
vector<string> c;
string buc;
for (int i = 0; i < le; i++)
{
if (p[i] == ' ')
{
c.push_back(buc);
buc = "";
}
else buc += p[i];
}
if (c.size() == 2) cout << "Prime\n";
else
{
c.back().pop_back();
cout << c.back() << '\n';
}
}
return 0;
}
:::