阶
阶:由欧拉定理可知,对于 a ∈ z a\in\mathbb{z} a∈z, m ∈ N ∗ m\in\mathbb{N}^{*} m∈N∗,若 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 an≡1(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 an≡1(modm),则 δ m ( a ) ∣ n \delta_m(a)\mid n δm(a)∣n。
性质3:设 m ∈ N ∗ m\in\mathbb{N}^{*} m∈N∗, a , b ∈ z a,b\in\mathbb{z} a,b∈z, 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} k∈N, m ∈ N ∗ m\in\mathbb{N}^{*} m∈N∗, a ∈ z a\in\mathbb{z} a∈z, 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}^{*} m∈N∗, a ∈ z a\in\mathbb{z} a∈z,若 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 m⩾3,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 21≡2,22≡4,23≡1(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 ax≡1(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.预处理
-
线性筛出不大于 m m m 的素数,并求出所有不大于 m m m的正整数的欧拉函数值。
-
对每个不大于 m m m的素数 p p p,求出所有不大于 m m m的 p α p^\alpha pα和 2 p α 2p^\alpha 2pα。(原根存在定理)
2.判定 n n n是否有原根
3.求最小原根
- 求出 φ ( m ) \varphi(m) φ(m)的所有因质数
- 枚举与 m m m互质的 i i i(原根定义)
- 对于 φ ( 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不是原根(原根判定定理)
- 继续循环,直到找到合适的 i i i为止
4.求所有原根
- 枚举 φ ( m ) \varphi(m) φ(m)以内的正整数 s s s
- 如果 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;
}