原根

前置知识1:欧拉定理

  • gcd(a,m)=1\gcd(a,m) =1,则 aϕ(m)1(modm)a^ { \phi(m) } \equiv 1\pmod m

因此当 gcd(a,m)=1\gcd(a,m) =1 时,存在一个最小正整数 nn 使得 an1(modm)a^ { n } \equiv 1\pmod m ,这个 nn 称作 aamm 的阶,记作 δm(a)\delta_m(a)

原根:gcd(a,m)=1\gcd(a,m) =1 ,且 δm(a)=ϕ(m)\delta_m(a)=\phi(m) ,则称 aa 为模 mm 的原根。

阶的性质:

  1. an1(modm)a^n \equiv 1 \pmod m ,则 δm(a)n\delta_m(a)|n
  • 证明:显然
  1. gcd(a,m)=gcd(b,m)=1\gcd(a,m)=\gcd(b,m)=1
    δm(ab)=δm(a)δm(b)\delta_m(ab)=\delta_m(a)\delta_m(b) 的充要条件是 gcd(δm(a),δm(b))=1\gcd(\delta_m(a),\delta_m(b))=1

证明:

  • 必要性:
    aδm(a)1(modm)a^ { \delta_m(a) } \equiv 1\pmod mbδm(b)1(modm)b^ { \delta_m(b) } \equiv 1\pmod m
    可知 (ab)lcm(δm(a),δm(b))1(modm)(ab)^ { { \rm lcm } (\delta_m(a),\delta_m(b)) } \equiv 1\pmod m
    δm(ab)lcm(δm(a),δm(b))\delta_m(ab)| { \rm lcm } (\delta_m(a),\delta_m(b))
    又因为 δm(ab)=δm(a)δm(b)\delta_m(ab)=\delta_m(a)\delta_m(b)
    δm(a)δm(b)lcm(δm(a),δm(b))\delta_m(a)\delta_m(b)| { \rm lcm } (\delta_m(a),\delta_m(b))
    gcd(δm(a),δm(b))=1\gcd(\delta_m(a),\delta_m(b))=1

  • 充分性:
    (ab)δm(ab)1(modm)(ab)^ { \delta_m(ab) } \equiv 1\pmod m 可知:
    1(ab)δm(ab)δm(b)aδm(ab)δm(b)×bδm(b)δm(ab)aδm(ab)δm(b)(modm)1\equiv (ab)^ { \delta_m(ab)\delta_m(b) } \equiv a^ { \delta_m(ab)\delta_m(b) } \times b^ { \delta_m(b)\delta_m(ab) } \equiv a^ { \delta_m(ab)\delta_m(b) } \pmod m
    δm(a)δm(ab)δm(b)\delta_m(a)|\delta_m(ab)\delta_m(b)
    又因为 gcd(δm(a),δm(b))=1\gcd(\delta_m(a),\delta_m(b))=1
    δm(a)δm(ab)\delta_m(a)|\delta_m(ab)
    同理,δm(b)δm(ab)\delta_m(b)|\delta_m(ab)
    又因为 gcd(δm(a),δm(b))=1\gcd(\delta_m(a),\delta_m(b))=1
    故有 δm(a)δm(b)δm(ab)\delta_m(a)\delta_m(b)|\delta_m(ab)
    另一方面, (ab)δm(a)δm(b)=(aδm(a))δm(b)×(bδm(b))δm(a)1(modm)(ab)^ { \delta_m(a)\delta_m(b) } =(a^ { \delta_m(a) } )^ { \delta_m(b) } \times (b^ { \delta_m(b) } )^ { \delta_m(a) } \equiv 1\pmod m
    δm(ab)δm(a)δm(b)\delta_m(ab)|\delta_m(a)\delta_m(b)
    综上, δm(ab)=δm(a)δm(b)\delta_m(ab)=\delta_m(a)\delta_m(b)

3.若 gcd(a,m)=1\gcd(a,m)=1 ,则 δm(ak)=δm(a)gcd(δm(a),k)\delta_m(a^k)=\frac { \delta_m(a) } { \gcd(\delta_m(a),k) }

  • 证明:
    akδm(ak)=(ak)δm(ak)1(modm)a^ { k\delta_m(a^k) } =(a^k)^ { \delta_m(a^k) } \equiv 1\pmod m
    δm(a)kδm(ak)\Rightarrow \delta_m(a)|k\delta_m(a^k)
    δm(a)gcd(δm(a),k)δm(ak)\Rightarrow \frac { \delta_m(a) } { \gcd(\delta_m(a),k) } |\delta_m(a^k)
    另一方面,
    (ak)δm(a)gcd(δm(a),k)=(aδm(a))kgcd(δm(a),k)1(modm)(a^k)^ { \frac { \delta_m(a) } { \gcd(\delta_m(a),k) } } =(a^ { \delta_m(a) } )^ { \frac { k } { \gcd(\delta_m(a),k) } } \equiv 1\pmod m
    δm(ak)δm(a)gcd(δm(a),k)\Rightarrow \delta_m(a^k)| { \frac { \delta_m(a) } { \gcd(\delta_m(a),k) } }
    综上,有 δm(ak)=δm(a)gcd(δm(a),k)\delta_m(a^k)=\frac { \delta_m(a) } { \gcd(\delta_m(a),k) }

回到正题,我们来思考怎样的数有原根。
首先,易证 m=1,2,4m=1,2,4 时有原根。
结论一:奇质数有原根

  • 证明:待补充

结论二:对于奇质数 ppαN\alpha\in \mathbb { N } ^*pαp^\alpha 有原根。

  • 证明:待补充

结论三:对于奇质数 ppαN\alpha\in\mathbb { N } ^*2pα2p^\alpha 有原根。

  • 证明:待补充

结论四:对于 m{1,2,4}m \in \{1,2,4\} 且不存在奇质数 ppαN\alpha\in\mathbb { N } ^* 使得 m{pα,2pα}m\in\{p^\alpha,2p^\alpha\} ,模 mm 的原根不存在。

  • 证明:待补充

综上,仅 1,2,4,pα,2pα1,2,4,p^\alpha,2p^\alpha ,其中 pp 为奇质数, αN\alpha\in\mathbb { N } ^*

如何求一个数 nn 的所有原根?
首先找到 nn 的最小原根,记为 gg ,则 nn 的所有原根均可由 gg 乘方得到。
具体的,若 nn 有原根,必有 ϕ(ϕ(n))\phi(\phi(n)) 个原根,每个都形如 gkmodng^k\mod n 的形式,要求满足 gcd(k,ϕ(n))=1\gcd(k,\phi(n))=1
于是,我们在求得 nn 的最小原根 gg 后便可 在 O(ϕ(n)logϕ(n))O(\phi(n)\log\phi(n)) 的时间内求得 nn 的所有原根。


如何求得 nn 的最小原根 gg
枚举即可。

  • 王元于 1959 年证明了若 nn 有原根,其最小原根是不多 n0.25n^ { 0.25 } 级别的。
    事实上,由大量试验数据可以发现,对于足够大的 nn,其最小正原根的大小不是多项式级别的。
    ——百度百科

根据这个结论,我们可以从小到大枚举。由原根的定义,若 gg 为模 nn 的原根,则对于 ϕ(n)\phi(n) 的任意素因数 pp,必有:
gϕ(n)/p≢1(modn)g^ { \phi(n)/p } \not\equiv 1\pmod n
并且,满足如上条件的 gg 必为 nn 的原根

我们可以预处理出 ϕ(n)\phi(n) 的所有质因数,快速幂来判断。

 problem link: \texttt { problem link: }
Luogu P6091 【模板】原根

 code \texttt { code }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <bits/stdc++.h>
using namespace std;
int n, _, d, prime[100010], np[1000010], phi[1000010], tot, mx;
int ans[500010], cnt;
vector<int> f;
int qp(long long x, int k, int mod) {
long long res = 1;
while (k) {
if (k & 1)
res = (res * x) % mod;
x = (x * x) % mod, k >>= 1;
}
return res;
}
int gcd(int x, int y) {
while (x) swap(x, y), x %= y;
return y;
}
bool check(int t) {
if (!(t & 3))
return false;
if (!(t & 1))
t >>= 1;
int x = np[t];
while (np[t] == x) t /= x;
return t == 1;
}
int main() {
phi[1] = 1;
for (int i = 2; i <= 1000000; i++) {
if (!np[i])
prime[++tot] = np[i] = i, phi[i] = i - 1;
for (int j = 1; j <= np[i] && i * prime[j] <= 1000000; j++) {
np[i * prime[j]] = prime[j];
if (prime[j] == np[i])
phi[i * prime[j]] = phi[i] * prime[j];
else
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
scanf("%d", &_);
while (_--) {
scanf("%d%d", &n, &d);
if (n == 2) {
printf(d == 1 ? "1\n1\n" : "1\n\n");
continue;
}
if (n == 4) {
printf(d == 1 ? "1\n3\n" : "1\n\n");
continue;
}
if (!check(n)) {
printf("0\n\n");
continue;
}
int k = phi[n], x;
f.clear(), cnt = 0;
while (k > 1) {
x = np[k], f.push_back(phi[n] / x);
while (np[k] == x) k /= x;
}
for (int i = 1;; i++) {
if (qp(i, phi[n], n) != 1)
continue;
bool flag = true;
for (auto j : f) {
if (qp(i, j, n) == 1) {
flag = false;
break;
}
}
if (flag) {
ans[++cnt] = i;
break;
}
}
printf("%d\n", phi[phi[n]]);
long long kk = ans[1];
for (int i = 1; i < phi[n] - 1; i++) {
kk = (kk * ans[1]) % n;
if (gcd(i + 1, phi[n]) == 1)
ans[++cnt] = kk;
}
sort(ans + 1, ans + 1 + cnt);
for (int i = d; i <= cnt; i += d) printf("%d ", ans[i]);
putchar('\n');
}
return 0;
}

实测将每次判 gcd(i + 1, phi[n]) == 1 改为预处理 ϕ(n)\phi(n) 质因数的倍数后,速度提升了数倍不止。
修改后的代码如下:
 code \texttt { code }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <bits/stdc++.h>
using namespace std;
int n, _, d, prime[100010], np[1000010], phi[1000010], tot, mx;
int ans[500010], cnt;
bool is[1000010], cant[1000010];
vector<int> f;
int qp(long long x, int k, int mod) {
long long res = 1;
while (k) {
if (k & 1)
res = (res * x) % mod;
x = (x * x) % mod, k >>= 1;
}
return res;
}
int gcd(int x, int y) {
while (x) swap(x, y), x %= y;
return y;
}
bool check(int t) {
if (!(t & 3))
return false;
if (!(t & 1))
t >>= 1;
int x = np[t];
while (np[t] == x) t /= x;
return t == 1;
}
int main() {
phi[1] = 1;
for (int i = 2; i <= 1000000; i++) {
if (!np[i])
prime[++tot] = np[i] = i, phi[i] = i - 1;
for (int j = 1; j <= np[i] && i * prime[j] <= 1000000; j++) {
np[i * prime[j]] = prime[j];
if (prime[j] == np[i])
phi[i * prime[j]] = phi[i] * prime[j];
else
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
scanf("%d", &_);
while (_--) {
scanf("%d%d", &n, &d);
if (n == 2) {
printf(d == 1 ? "1\n1\n" : "1\n\n");
continue;
}
if (n == 4) {
printf(d == 1 ? "1\n3\n" : "1\n\n");
continue;
}
if (!check(n)) {
printf("0\n\n");
continue;
}
int k = phi[n], x;
long long kk;
f.clear(), cnt = 0;
while (k > 1) {
x = np[k], f.push_back(phi[n] / x);
while (np[k] == x) k /= x;
for (int i = x; i <= n; i += x) cant[i] = true;
}
for (int i = 1;; i++) {
if (qp(i, phi[n], n) != 1)
continue;
bool flag = true;
for (auto j : f) {
if (qp(i, j, n) == 1) {
flag = false;
break;
}
}
if (flag) {
is[i] = true, kk = x = i;
break;
}
}
printf("%d\n", phi[phi[n]]);
for (int i = 1; i < phi[n] - 1; i++) {
kk = (kk * x) % n;
if (!cant[i + 1])
is[kk] = true;
}
cnt = 0;
for (int i = 1; i <= n; i++) {
if (is[i]) {
is[i] = false;
if (++cnt == d)
printf("%d ", i), cnt = 0;
}
cant[i] = false;
}
putchar('\n');
}
return 0;
}

 that’s all. \texttt { that's all. }


原根
https://cyb1010.github.io/2023/09/18/数学/原根/
作者
cyb1010
发布于
2023年9月18日
许可协议