【数学】原根

一个数有原根时,有 \(\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;
}

上面这个算法可以找到所有的原根,复杂度爆炸。
找一个最小的原根非常快,先判断是否存在原根,通过质因数分解判断,然后用质因数分解求出欧拉函数,再对欧拉函数进行质因数分解,然后套一堆快速幂。

posted @ 2021-01-16 22:39  purinliang  阅读(686)  评论(0编辑  收藏  举报