对于任意质数 和 ,其中 ,满足:
根据裴蜀定理,可以知道 与 是一一对应的,因此:
这个定理并没有逆定理,所以不能直接用于素性检验。但是值的注意的是,它能排除掉很大一部分合数。
对于质数 和 ,有以下性质:
如果满足:
则有:
其实很简单,就让读者自行证明了。
根据 ,可以知道 。
然后根据平方差公式可得:。
因为 是质数,所以 和 中,至少有一个是 的倍数。
也就是 。
其实就是把费马小定理和二次探测定理结合了一下。
对于一个奇数 ,我们令 ,其中 是奇数。
我们先随机一个数 ,计算出 。
然后将 平方 次,每次平方的时候用二次探测定理判断一下。
然后经过 次平方的 变成了 ,这个时候就可以用费马小定理判断了。
假设你随机了 次,那么判断错误的概率就是 。
所以多随几次就可以了。
对于 OI 来说,我们可以考虑不随机,而是使用固定的数来素性检验。
判断 int
范围内的质数只要前 个质数。
判断 范围内的质数只要前 个质数。
判断 long long
范围内的质数只要前 个质数。
接下来给出本题范围内(即 )的 Miller Rabin 代码:
using ll = long long;
ll mul(ll a, ll b, ll mod){
ll r = a * b - mod * (ll)(1.L / mod * a * b);
return r - mod * (r >= mod) + mod * (r < 0);
}
ll qpow(ll a, ll b, ll mod) {
ll res(1);
for (; b; b >>= 1, a = mul(a, a, mod))
if (b & 1) res = mul(res, a, mod);
return res;
}
bool is_prime(ll n) {
if (n <= 1) return false;
vector<ll> base = {2, 3, 5, 7, 11, 13, 17, 19, 23};
for (ll p : base) {
if (n == p) return true;
if (n % p == 0) return false;
}
ll m = (n - 1) >> __builtin_ctz(n - 1);
for (ll p : base) {
ll t = m, a = qpow(p, m, n);
while (t != n - 1 && a != 1 && a != n - 1)
a = mul(a, a, n), t *= 2;
if (a != n - 1 && t % 2 == 0) return false;
}
return true;
}
值的一提的是,上述代码中的乘法没有使用 int128
,而是使用了 long double
,这样会极大得提升运算效率。
而且 is_prime
函数并没有完全按照之前算法流程来写,而是稍加改动,但是本质上是相同的。
在 个数中,每次随机选取 个数,期望 次出现重复的数。
不太会,大概用斯特林公式估算一下。
首先考虑对于一个合数 找到它的一个因子 ,满足 。
对于 的一个奇质因子 ,我们把 到 中每个数 向 连边,这样构成了一个基环森林。
不难发现这个基环森林有一些性质:
我们考虑两个指针 和 ,一开始指向同一个随机节点,然后每次 走 步, 走 步。
经过 步 和 都会到达环上,而根据追及问题最多再经过环的大小步, 和 就必定会重合,也就是经过期望 步, 和 就会重合。
我们将 和 放在 的意义下,那么就是经过期望 步,,此时的 还不能作为最终结果,因为还要考虑 的情况,但是这种情况的发生概率很小,所以直接重新选择起始点即可。
另外的,对于质因子 可能需要特判。
这样时间复杂度就是 的,然而 的最小质因子 ,所以时间复杂度也可以看作是 的。
我们可以设置一个块长 ,把每 步的 累乘起来,一起取 判断,要注意的是,如果累乘起来的变量会变为 ,就不要乘上去, 一般取 或者 ,但是好像没有一定要取 的幂次这种说法。
当遇到了 的情况,说明走完了模 意义下的一大圈,其他位置都是 ,而只有这个位置上 ,所以我们再随另一个起始点即可。
直接递归,找因子前先判断一下是否是质数即可。
/*
* @Author: ftt2333
* @Email: ftt2333@126.com
* @LastEditTime: 2022-12-21 22:18:29
*/
#include <bits/stdc++.h>
using namespace std;
#ifndef LOCAL
void debug(...) { }
#endif
#define all(v) (v).begin(), (v).end()
using ll = long long;
using lll = __int128;
mt19937_64 rnd(114514);
ll mul(ll a, ll b, ll mod){
ll r = a * b - mod * (ll)(1.L / mod * a * b);
return r - mod * (r >= mod) + mod * (r < 0);
}
ll add(ll a, ll b, ll mod) {
a += b;
return a >= mod ? a - mod : a;
}
ll sub(ll a, ll b, ll mod) {
a -= b;
return a < 0 ? a + mod : a;
}
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
ll qpow(ll a, ll b, ll mod) {
ll res(1);
for (; b; b >>= 1, a = mul(a, a, mod))
if (b & 1) res = mul(res, a, mod);
return res;
}
bool is_prime(ll n) {
if (n <= 1) return false;
vector<ll> base = {2, 3, 5, 7, 11, 13, 17, 19, 23};
for (ll p : base) {
if (n == p) return true;
if (n % p == 0) return false;
}
ll m = (n - 1) >> __builtin_ctz(n - 1);
for (ll p : base) {
ll t = m, a = qpow(p, m, n);
while (t != n - 1 && a != 1 && a != n - 1)
a = mul(a, a, n), t *= 2;
if (a != n - 1 && t % 2 == 0) return false;
}
return true;
}
ll get_factor(ll n) {
if (n % 2 == 0) return 2;
auto f = [&](ll x) { return add(mul(x, x, n), 1, n); };
ll x = 0, y = 0, tot = 0, p = 1, q, g;
for (ll i = 0; (i & 0xff) || (g = gcd(p, n)) == 1; i++, x = f(x), y = f(f(y))) {
if (x == y) x = tot++, y = f(x);
q = mul(p, sub(x, y, n), n);
if (q) p = q;
}
return g;
}
vector<ll> solve(ll n) {
if (n == 1) return {};
if (is_prime(n)) return {n};
ll d = get_factor(n);
auto v1 = solve(d), v2 = solve(n / d);
auto i1 = v1.begin(), i2 = v2.begin();
vector<ll> ans;
while (i1 != v1.end() || i2 != v2.end()) {
if (i1 == v1.end()) ans.push_back(*i2++);
else if (i2 == v2.end()) ans.push_back(*i1++);
else {
if (*i1 < *i2) ans.push_back(*i1++);
else ans.push_back(*i2++);
}
}
return ans;
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t; cin >> t;
while (t--) {
ll x; cin >> x;
auto res = solve(x);
if (res.size() <= 1) cout << "Prime\n";
else cout << res.back() << '\n';
}
}