【数学】原根

一个数有原根时,有 φ(φ(m)) 个原根。

有原根的充要条件是, m=2,4,pn,2pn 其中p是奇质数。

求一个最小的原根,从 1 枚举到 m0.25 ,然后若 gcd(i,m)=1 并且对于 φ(m) 的所有质因数 p ,都有 iφ(m)p1 ,那么i就是最小的原根。

找到一个最小的原根之后,可以通过这个最小的原根找到其他的原根。

设最小的原根为 g 那么,从 1 遍历到 φ(m) ,假如 gi=1 那么,gi也是 一个原根。

多次求原根

1、预处理出所需范围内的质数、质数幂、欧拉函数, O(MAXM)
2、给出要求原根的数 m ,判断 m 是否有原根,若没有则返回, O(1)
3、求出φ(m) ,并对 φ(m) 分解质因数, O(logm)
4、从 1 开始暴力枚举,根据伯吉斯的证明,最坏情况下枚举到 m0.25 会得到第一个原根,O(m0.25log2m)
5、用第一个原根求出其他所有的原根 O(φ(m)logm)

时间复杂度 O(M+k(m0.25log2m))

验证:https://www.luogu.com.cn/problem/P6091

copyconst 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 @   purinliang  阅读(797)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示