洛谷P6091(原根模板)

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

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

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

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

性质3:设 m ∈ N ∗ m\in\mathbb{N}^{*} mN a , b ∈ z a,b\in\mathbb{z} a,bz gcd ⁡ ( a , m ) = gcd ⁡ ( b , m ) = 1 \gcd(a,m)=\gcd(b,m)=1 gcd(a,m)=gcd(b,m)=1,则 δ m ( a b ) = δ 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))=1 gcd(δm(a),δm(b))=1

性质4:设 k ∈ N k\in\mathbb{N} kN m ∈ N ∗ m\in\mathbb{N}^{*} mN a ∈ z a\in\mathbb{z} az gcd ⁡ ( a , m ) = 1 \gcd(a,m)=1 gcd(a,m)=1,则 δ m ( a k ) = δ m ( a ) g c d ( ( δ 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}^{*} mN a ∈ z a\in\mathbb{z} az,若 gcd ⁡ ( a , m ) = 1 \gcd(a,m)=1 gcd(a,m)=1,且 δ m ( a ) = φ ( m ) \delta_m(a)=\varphi(m) δm(a)=φ(m),则称 a a a为模 m m m的原根。

原根判定定理

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

原根个数

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

原根存在定理

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

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

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

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

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

总结

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

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

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

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

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

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

2.判定 n n n是否有原根
3.求最小原根
  1. 求出 φ ( m ) \varphi(m) φ(m)的所有因质数
  2. 枚举与 m m m互质的 i i i原根定义
  3. 对于 φ ( m ) \varphi(m) φ(m)的每个质因数 j j j,分别计算 i φ ( m ) j i^{{\frac{\varphi(m)}{j}}} ijφ(m),如果 i φ ( m ) j ≡ 1 ( m o d m ) i^{{\frac{\varphi(m)}{j}}} \equiv1\pmod{m} ijφ(m)1(modm)说明 i i i不是原根(原根判定定理
  4. 继续循环,直到找到合适的 i i i为止
4.求所有原根
  1. 枚举 φ ( m ) \varphi(m) φ(m)以内的正整数 s s s
  2. 如果 s s s φ ( m ) \varphi(m) φ(m)互质,则 a s a^s as一个原根(阶的性质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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kunyuwan

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

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

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

打赏作者

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

抵扣说明:

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

余额充值