P6091 【模板】原根 题解
作为一个刚刚学会原根求法顺便卡到最优解榜三的数论蒟蒻,来这里给跟我一样对着题解区的长篇大论一头雾水的朋友们指指路,分享一种
(前置知识:欧拉函数)
0. 原根是啥?有啥用?
引用一段典中典:
你说的对,但是感觉不如原根。原根是数论中一个非常重要的概念。设
g,n 是正整数,若g 模n 的阶等于\varphi(n) ,即当且仅当i = \varphi(n) 时g^i \equiv 1 \pmod n ,则称g 为n 的一个原根。对于0 < i \le \varphi(n) ,g^i \bmod n 的结果两两不同。我现在每天用原根都能做10^5 次数据规模10^6 的 NTT,也就是现实生活中10^{17} 次乘法运算,这便是原根给我的骄傲的资本。
至于“阶”的概念,理解成
1. 什么样的数有原根?
结论:
打个暴力就可以粗略地验证了,实在想看证明的话可以移步隔壁数竞大佬的题解
2. 怎么判断一个数是否为另一个数的原根?
首先肯定要满足
而如何判断
3. 一个数的原根有多少个?
由裴蜀定理可知,
4. 如何求出一个数的所有原根?
#include <bits/extc++.h>
inline int qpow(long long a, int b, int mod) {
long long res = 1;
for (; b; b >>= 1, a = a * a % mod)
if (b & 1)
res = res * a % mod;
return res;
}
int main() {
int T;
std::cin >> T;
while (T--) {
int n, d;
std::cin >> n >> d;
if (n == 2 || n == 4) {
puts("1");
if (d == 1)
printf("%d", n - 1);
putchar('\n');
continue;
}
int num = n;
int z = __builtin_ctz(num);
if (z >= 2) {
puts("0\n");
continue;
}
num >>= z;
// 对 n 分解质因数
int k = 3;
while (num % k && k * k <= num)
k++;
if (k * k > num)
k = num;
while (num % k == 0)
num /= k;
if (num != 1) {
puts("0\n");
continue;
}
// 对 phi(n) 分解质因数
int phi = n / k * (k - 1) >> z;
num = phi;
std::vector<int> v;
for (int i = 2; i * i <= num; i++)
if (num % i == 0) {
v.emplace_back(i);
while (num % i == 0)
num /= i;
}
if (num != 1)
v.emplace_back(num);
// 查找最小原根
int g = 2;
while (qpow(g, phi, n) != 1 || std::any_of(v.begin(), v.end(), [&](int p) { return qpow(g, phi / p, n) == 1; }))
g++;
// 筛出与 phi(n) 互质的数,标记所有原根
constexpr int N = 1e6;
std::bitset<N> vis, ans;
for (int i = 1, t = g; i < phi; i++, t = 1ll * t * g % n) {
if (!vis[i])
ans[t] = 1;
for (int p : v) {
if (1ll * i * p >= phi)
break;
vis[i * p] = 1;
if (i % p == 0)
break;
}
}
// 输出答案
printf("%d\n", ans.count());
int i = ans._Find_first();
for (int _ = d; --_;) // 这是重复 (d - 1) 次
i = ans._Find_next(i);
while (i != N) {
printf("%d ", i);
for (int _ = d; _--;) // 这是重复 d 次
i = ans._Find_next(i);
}
putchar('\n');
}
}