【数学】原根
一个数有原根时,有 \(\varphi(\varphi(m))\) 个原根。
有原根的充要条件是, \(m=2,4,p^n,2p^n\) 其中p是奇质数。
求一个最小的原根,从 \(1\) 枚举到 \(m^{0.25}\) ,然后若 \(gcd(i,m)=1\) 并且对于 \(\varphi(m)\) 的所有质因数 \(p\) ,都有 \(i^{\frac{\varphi(m)}{p}}\neq 1\) ,那么i就是最小的原根。
找到一个最小的原根之后,可以通过这个最小的原根找到其他的原根。
设最小的原根为 \(g\) 那么,从 \(1\) 遍历到 \(\varphi(m)\) ,假如 \(g^i=1\) 那么,\(g^i\)也是 一个原根。
多次求原根
1、预处理出所需范围内的质数、质数幂、欧拉函数, \(O(MAXM)\)
2、给出要求原根的数 \(m\) ,判断 \(m\) 是否有原根,若没有则返回, \(O(1)\)
3、求出\(\varphi(m)\) ,并对 \(\varphi(m)\) 分解质因数, \(O(\log m)\)
4、从 \(1\) 开始暴力枚举,根据伯吉斯的证明,最坏情况下枚举到 \(m^{0.25}\) 会得到第一个原根,\(O(m^{0.25}\log^2m)\)
5、用第一个原根求出其他所有的原根 \(O(\varphi(m)\log m)\)
时间复杂度 \(O(M+k*(m^{0.25}\log^2m))\)
验证:https://www.luogu.com.cn/problem/P6091
const int MAXN = 1e6 + 5;
int p[MAXN], ptop;
int pm[MAXN], pk[MAXN];
int phi[MAXN];
void sieve(int n) {
pm[1] = 1, pk[1] = 1;
phi[1] = 1;
for(int i = 2; i <= n; ++i) {
if(!pm[i]) {
p[++ptop] = i, pm[i] = i, pk[i] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= ptop; ++j) {
int t = i * p[j];
if(t > n)
break;
pm[t] = p[j];
if(i % p[j]) {
pk[t] = pk[p[j]];
phi[t] = phi[i] * phi[p[j]];
} else {
pk[t] = pk[i] * p[j];
if(pk[t] == t)
phi[t] = t - t / p[j];
else
phi[t] = phi[t / pk[t]] * phi[pk[t]];
break;
}
}
}
}
int qpow(ll x, int n, int mod) {
ll res = 1;
while(n) {
if(n & 1)
res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
vi getPrimeFactors(int n) {
vi res;
for(int i = 1; p[i]*p[i] <= n; ++i) {
if(n % p[i] == 0) {
res.eb(p[i]);
while(n % p[i] == 0)
n /= p[i];
}
}
if(n > 1)
res.eb(n);
return res;
}
int getMinPrimitiveRoot(int n) {
if(!(n == 2 || n == 4 || pk[n] == n || (n % 2 == 0 && pk[n / 2] == n / 2)))
return -1;
vi pfs = getPrimeFactors(phi[n]);
for(int i = 1;; ++i) {
if(qpow(i, phi[n], n) == 1) {
bool ok = 1;
for(const int &pf : pfs) {
if(qpow(i, phi[n] / pf, n) == 1) {
ok = 0;
break;
}
}
if(ok)
return i;
}
}
exit(-1);
}
vi getAllPrimitiveRoot(int n) {
vi res;
int g = getMinPrimitiveRoot(n);
if(g != -1) {
res.reserve(phi[n]);
for(int i = 1; i <= phi[n]; ++i) {
if(__gcd(i, phi[n]) == 1)
res.eb(qpow(g, i, n));
}
srt(res);
}
return res;
}
上面这个算法可以找到所有的原根,复杂度爆炸。
找一个最小的原根非常快,先判断是否存在原根,通过质因数分解判断,然后用质因数分解求出欧拉函数,再对欧拉函数进行质因数分解,然后套一堆快速幂。