洛谷P6091(原根模板)

本文深入探讨了模数计算中的欧拉定理和原根概念。原根是指模数下满足特定条件的元素,其阶是对应循环节的最短长度。文章详细阐述了原根的性质、判定定理、存在性和计算方法,为理解模数运算提供了关键理论支持。

阶:由欧拉定理可知,对于a∈za\in\mathbb{z}azm∈N∗m\in\mathbb{N}^{*}mN,若gcd⁡(a,m)=1\gcd(a,m)=1gcd(a,m)=1,则aφ(m)≡1(modm)a^{\varphi(m)}\equiv 1\pmod maφ(m)1(modm)

因此满足同余式an≡1(modm)a^n\equiv 1 \pmod man1(modm)的最小整数nnn存在,这个n称作aaammm的阶,记作δm(a)\delta_m(a)δm(a)

性质1a,a2,⋯ ,aδm(a)a,a^2,\cdots,a^{\delta_m(a)}a,a2,,aδm(a)mmm两两不同余

性质2an≡1(modm)a^n\equiv 1\pmod man1(modm),则δm(a)∣n\delta_m(a)\mid nδm(a)n

性质3:设m∈N∗m\in\mathbb{N}^{*}mNa,b∈za,b\in\mathbb{z}a,bzgcd⁡(a,m)=gcd⁡(b,m)=1\gcd(a,m)=\gcd(b,m)=1gcd(a,m)=gcd(b,m)=1,则δm(ab)=δm(a)δm(b)\delta_m(ab)=\delta_m(a)\delta_m(b)δm(ab)=δm(a)δm(b)的充分必要条件是gcd⁡(δm(a),δm(b))=1\gcd(\delta_m(a),\delta_m(b))=1gcd(δm(a),δm(b))=1

性质4:设k∈Nk\in\mathbb{N}kNm∈N∗m\in\mathbb{N}^{*}mNa∈za\in\mathbb{z}azgcd⁡(a,m)=1\gcd(a,m)=1gcd(a,m)=1,则δm(ak)=δm(a)gcd((δm(a),k)\delta_m(a^k)=\dfrac{\delta_m(a)}{gcd\big(\big(\delta_m(a),k\big)}δm(ak)=gcd((δm(a),k)δm(a)

原根

原根:设m∈N∗m\in\mathbb{N}^{*}mNa∈za\in\mathbb{z}az,若gcd⁡(a,m)=1\gcd(a,m)=1gcd(a,m)=1,且δm(a)=φ(m)\delta_m(a)=\varphi(m)δm(a)=φ(m),则称aaa为模mmm的原根。

原根判定定理

原根判定定理:设m⩾3,gcd⁡(a,m)=1m \geqslant 3,\gcd(a,m)=1m3,gcd(a,m)=1,则aaa是模mmm的原根的充要条件,对于φ(m)\varphi(m)φ(m)的每个素因数ppp,都有aφ(m)p≢1(modm)a^{\frac{\varphi(m)}{p}}\not\equiv 1\pmod mapφ(m)1(modm)

原根个数

若一个数mmm有原根,则它原根的个数为φ(φ(m))\varphi(\varphi(m))φ(φ(m))

原根存在定理

原根存在定理:一个数mmm存在原根当且仅当m=2,4,pα,2pαm=2,4,p^{\alpha},2p^{\alpha}m=2,4,pα,2pα,其中ppp为奇素数,α∈N∗\alpha\in \mathbb{N}^{*}αN

定理1:对于奇素数pppppp有原根。

定理2:对于奇素数pppα∈N∗\alpha \in \mathbb{N}^{*}αNpαp^\alphapα有原根。

定理3:对于奇素数pppα∈N∗\alpha \in \mathbb{N}^{*}αN2pα2p^\alpha2pα有原根。

定理4 :对于m≠2,4m\ne 2,4m=2,4,且不存在奇素数pppα∈N∗\alpha \in \mathbb{N}^{*}αN使得m=pα,2pαm=p^\alpha,2p^\alpham=pα,2pα, 模mmm的原根不存在。

总结

根据欧拉定理我们可以知道,若a,ma,ma,m互质,aφ(m)≡1(modm)a^{\varphi(m)}\equiv 1 \pmod maφ(m)1(modm)。因此,a1,a2,...a^1,a^2,...a1,a2,...这样一个数列在模mmm意义下将有一个φ(m)\varphi(m)φ(m)长度的循环节,因为aφ(m)+1a^{\varphi(m)+1}aφ(m)+1又回到了一开始的a1a^1a1

然而,我们并不能保证它是最短的循环节,例如,21≡2,22≡4,23≡1(mod7)2^1\equiv 2,2^2\equiv4,2^3\equiv 1 \pmod 7212,224,231(mod7),可以发现这时候最短循环节长度为333,而不是φ(7)=6\varphi(7)=6φ(7)=6。我们把这个最短循环节的长度定义为aaa在模mmm下的,记住δm(a)\delta_m(a)δm(a)。严格地,定义aaa在模mmm下的是同余方程ax≡1(modm)a^x\equiv 1\pmod max1(modm)最小正整数解

显然,δm(a)\delta_m(a)δm(a)一定是φ(m)\varphi(m)φ(m)的因数。特别地,当δm(a)=φ(m)\delta_m(a)=\varphi(m)δm(a)=φ(m),就称aaa为模mmm下的一个原根,对于原根aaa来说,a1,a2,...,aφ(m)a^1,a^2,...,a^{\varphi(m)}a1,a2,...,aφ(m)在模mmm下各不相同,他们就是最短的循环节

求解nnn的所有原根的步骤为:

1.预处理
  1. 线性筛出不大于 mmm 的素数,并求出所有不大于mmm的正整数的欧拉函数值。

  2. 对每个不大于mmm的素数ppp,求出所有不大于mmmpαp^\alphapα2pα2p^\alpha2pα。(原根存在定理

2.判定nnn是否有原根
3.求最小原根
  1. 求出φ(m)\varphi(m)φ(m)的所有因质数
  2. 枚举与mmm互质的iii原根定义
  3. 对于φ(m)\varphi(m)φ(m)的每个质因数jjj,分别计算iφ(m)ji^{{\frac{\varphi(m)}{j}}}ijφ(m),如果iφ(m)j≡1(modm)i^{{\frac{\varphi(m)}{j}}} \equiv1\pmod{m}ijφ(m)1(modm)说明iii不是原根(原根判定定理
  4. 继续循环,直到找到合适的iii为止
4.求所有原根
  1. 枚举φ(m)\varphi(m)φ(m)以内的正整数sss
  2. 如果sssφ(m)\varphi(m)φ(m)互质,则asa^sas一个原根(阶的性质4

洛谷P6091(求模m意义下的原根)

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define Buff std::ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
#define lowbit(x) (x & (-x))
#define ls (rt * 2)
#define rs (rt * 2 + 1)
typedef long long ll;
const ll mod = 1000000007;
const ll MAXN = 1e7 + 10;
const double eps = 1e-8;
//======================================
vector<ll> primes;
bitset<MAXN> st, exist;
ll euler[MAXN];

//求最小公约数
ll gcd(ll a, ll b) {
    return b ? gcd(b, a % b) : a;
}

//快速幂
ll qpow(ll a, ll k, ll p) {
    ll res = 1;
    while (k) {
        if (k & 1) res = res * a % p;
        a = a * a % p;
        k >>= 1;
    }
    return res;
}
//把1~n当中的素数筛选出来,同时把1~n的欧拉函数值求出来
void get_eulers(ll n) {
    euler[1] = 1;
    for (ll i = 2; i <= n; i++) {
        if (!st[i]) primes.push_back(i), euler[i] = i - 1;
        for (ll j = 0; primes[j] <= n / i; j++) {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) {
                euler[i * primes[j]] = euler[i] * primes[j];
                break;
            }
            euler[i * primes[j]] = euler[i] * (primes[j] - 1);
        }
    }
}
//标记是否存在原根
void init() {
    exist[1] = exist[2] = exist[4] = 1;
    for (auto x : primes) {
        if (x % 2) {
            for (ll i = x; i < MAXN; i *= x) {
                exist[i] = 1;
                if (i * 2 < MAXN) exist[i * 2] = 1;
            }
        }
    }
}
//分解质因数
vector<ll> get_primesfactor(ll x) {
    vector<ll> res;
    // for (ll i = 2; i <= x / i; i++)
    //     if (x % i == 0) {
    //         res.push_back(i);
    //         while (x % i == 0) x /= i;
    //     }
    // if (x > 1) res.push_back(x);
    for (auto prime : primes) {
        if (prime > x) break;
        if (x % prime == 0) res.push_back(prime);
    }
    return res;
}
//求解质因数
vector<ll> get_primative_roots(ll m) {
    vector<ll> v;
    //第二步
    if (!exist[m]) return v;
    ll phi = euler[m], fst;
    //第三步的第一小步
    auto factors = get_primesfactor(phi);
    //第三步的第二小步
    for (ll i = 1;; i++) {
        if (gcd(i, m) != 1) continue;
        bool ok = true;
        //第三步的第三小步
        for (auto x : factors) {
            if (qpow(i, phi / x, m) == 1) {
                ok = false;
                break;
            }
        }
        if (ok) {
            fst = i;
            break;
        }
    }
    //第四步
    ll cur = fst;
    for (ll i = 1; i <= phi; i++) {
        if (gcd(phi, i) == 1) v.push_back(cur);
        cur = cur * fst % m;
    }
    return v;
}
// int get_minimum_primitive_root(int m) {
//     if (!exist[m]) return 0;
//     int phi = euler[m];
//     for (int i = 1;; i++) {
//         if (gcd(i, m) != 1) continue;
//         auto factors = get_primesfactor(phi);
//         bool ok = true;
//         for (auto x : factors)
//             if (qpow(i, phi / x, m) == 1) {
//                 ok = false;
//                 break;
//             }
//         if (ok) return i;
//     }
// }
int main() {
    Buff;
    //     clock_t c1 = clock();
    // #ifdef LOCAL
    //     freopen("in.in", "r", stdin);
    //     freopen("out.out", "w", stdout);
    // #endif
    //=========================================
    ll t;
    ll n = 1000000;
    //第一步的第一小步
    get_eulers(n);
    //第一步的第二小步
    init();
    cin >> t;
    while (t--) {
        ll n, d, m, s, t, x, id, j;
        cin >> n >> d;
        auto ans = get_primative_roots(n);
        ll len = ans.size();
        sort(ans.begin(), ans.end());
        cout << len << endl;
        for (int i = d; i <= len; i += d) {
            cout << ans[i - 1] << " ";
        }
        cout << endl;
    }
    //=========================================
    // cerr << "Time Used:" << clock() - c1 << "ms" << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kunyuwan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值