原根

定义:设 m > 1 , ( a , m ) = 1 m>1,(a,m)=1 m>1,(a,m)=1,使得 a l ≡ 1 ( m o d    m ) a^l \equiv 1(mod~~m) al1(mod  m)成立的最小 l l l,称为 a a a对模 p p p的阶,记做 δ m ( a ) \delta_m(a) δm(a)

定理:若 a n ≡ 1 ( m o d    m ) , m > 1 , ( a , m ) = 1 a^n \equiv 1(mod~~m),m>1,(a,m)=1 an1(mod  m),m>1,(a,m)=1,则 δ m ( a ) ∣ n \delta_m(a)|n δm(a)n

原根

定义:设 a , m a,m a,m均为正整数,若 a a a m m m的阶等于 φ ( m ) \varphi(m) φ(m),则称 a a a为模 m m m的一个原根。

推论:

  • 设一个数 g g g对于模素数 p p p来说是原根,那么 g i ( m o d    p ) , 1 < g < p , 0 ≤ i < p g^i(mod~~p),1<g<p,0 \leq i<p gi(mod  p),1<g<p,0i<p的结果两两不同。
  • 若模 m m m有原根,那么一共有 φ ( φ ( m ) ) \varphi(\varphi(m)) φ(φ(m))个。

m m m有原根的充要条件为 m = 2 , 4 , p k , 2 ∗ p k ( p ∈ p r i m e s , p > 2 ) m=2,4,p^k,2*p^k(p \in primes,p>2) m=2,4,pk,2pk(pprimes,p>2)

m m m的原根

m m m为素数

m m m为素数 p p p,其欧拉函数值为 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p1,对 p − 1 p-1 p1质因数分解,即 p − 1 = p 1 a 1 p 2 a 2 . . . p a k k p-1=p_1^{a_1}p_2^{a_2}...p^k_{a_k} p1=p1a1p2a2...pakk,若恒有 g p − 1 p i ≠ 1 ( m o d    p ) g^{\frac{p-1}{p_i}} \neq 1(mod~~p) gpip1=1(mod  p)成立,则 g g g p p p的原根。

以下代码是寻找 i n t int int范围内单个素数的原根。

ector<int> prime, fac, ans;
bitset<maxn> vis;

void euler() {
    vis.reset(), prime.clear();
    for (int i = 2; i < maxn; i++) {
        if (!vis[i]) prime.push_back(i);
        for (int j = 0; j < prime.size() && i * prime[j] < maxn; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
        }
    }
}

void getFac(int n) {
    fac.clear();
    int m = sqrt(n + 0.5);
    for (int i = 0; i < prime.size() && prime[i] <= m; i++) {
        if (n % prime[i] == 0) {
            fac.push_back(prime[i]);
            while (n % prime[i] == 0) n /= prime[i];
        }
    }
    if (n > 1) fac.push_back(n);
}

ll qkp(ll x, ll n, ll p) {
    ll ans = 1;
    while (n) {
        if (n & 1) ans = ans * x % p;
        x = x * x % p;
        n >>= 1;
    }
    return ans;
}

int solve(int p) {
    getFac(p - 1);
    for (int g = 2; g < p; g++) {
        bool ok = 1;
        for (int i = 0; i < fac.size(); i++) {
            int t = (p - 1) / fac[i];
            if (qkp(g, t, p) == 1) {
                ok = 0;
                break;
            }
        }
        if (ok) return g;   //求最小原根
        //if (ok) ans.push_back(g);  //求所有原根
    }
}

m m m为合数

算法思想:

  • 判断什么样的数才有原根: { 2 , 4 , p k , 2 ∗ p k    ∣     p ∈ p r i m e s , p > 2 } \{2,4,p^k,2*p^k~~|~~~p\in primes,p>2\} {2,4,pk,2pk     pprimes,p>2}

  • 找到一个数的最小原根,设为 g g g,则 m m m的所有原根都可以由 g g g的若干次乘方得到,即都能写成 g k ( m o d    p ) g^k(mod~~p) gk(mod  p)的形式,但是需要注意 g c d ( k , φ ( m ) ) = 1 gcd(k,\varphi(m))=1 gcd(k,φ(m))=1

最小原根的求法:

从小到大枚举 g g g并检验是否为 m m m的原根:对 φ ( n ) \varphi(n) φ(n)质因数分解,即 φ ( m ) = p 1 a 1 p 2 a 2 . . . p a k k \varphi(m)=p_1^{a_1}p_2^{a_2}...p^k_{a_k} φ(m)=p1a1p2a2...pakk,若恒有 g φ ( m ) p i ≠ 1 ( m o d    p ) g^{\frac{\varphi(m)}{p_i}} \neq 1(mod~~p) gpiφ(m)=1(mod  p)成立,则 g g g m m m的原根。找到最小原根后枚举 k ∈ [ 1 , φ ( m ) ] k\in [1,\varphi(m)] k[1,φ(m)],对于满足 g c d ( k , φ ( m ) ) = 1 gcd(k,\varphi(m))=1 gcd(k,φ(m))=1,那么 g k    m o d    m g^k~~mod~~m gk  mod  m即为其中一个原根。

vector<int> prime, fac, ans;
bitset<maxn> vis, has_root;
ll phi[maxn];

void init() {
    vis.reset(), prime.clear();
    phi[1] = 1;
    for (int i = 2; i < maxn; i++) {
        if (!vis[i]) {
            prime.push_back(i);
            phi[i] = i - 1;
        }
        for (int j = 0; j < prime.size() && i * prime[j] < maxn; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j]) {
                phi[i * prime[j]] = phi[i] * phi[prime[j]];
            } else {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
        }
    }
    has_root.reset();
    has_root[2] = has_root[4] = 1;
    for (int i = 1; i < prime.size(); i++) {
        for (ll j = prime[i]; j < maxn; j *= prime[i]) has_root[j] = 1;
        for (ll j = 2 * prime[i]; j < maxn; j *= prime[i]) has_root[j] = 1;
    }
}

void getFac(int n) {
    fac.clear();
    int m = sqrt(n + 0.5);
    for (int i = 0; i < prime.size() && prime[i] <= m; i++) {
        if (n % prime[i] == 0) {
            fac.push_back(prime[i]);
            while (n % prime[i] == 0) n /= prime[i];
        }
    }
    if (n > 1) fac.push_back(n);
}

ll qkp(ll x, ll n, ll p) {
    ll ans = 1;
    x %= p;
    while (n) {
        if (n & 1) ans = ans * x % p;
        x = x * x % p;
        n >>= 1;
    }
    return ans;
}

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

bool check(int g, int p) {
    if (qkp(g, phi[p], p) != 1) return 0;
    for (int i = 0; i < fac.size(); i++) {
        if (qkp(g, phi[p] / fac[i], p) == 1) return 0;
    }
    return 1;
}

int solve(int m) {
    getFac(phi[m]);
    for (int g = 1; g < m; g++) {
        if (check(g, m)) return g;
    }
}

void getAll(int m) {
    int g = solve(m);
    ll res = 1;
    ans.clear();
    for (int i = 1; i <= phi[m]; i++) {
        res = res * g % m;
        if (gcd(i, phi[m]) == 1) {
            ans.push_back(res);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值