前置知识1:欧拉定理
若 gcd ( a , m ) = 1 \gcd(a,m) =1 g cd( a , m ) = 1 ,则 a ϕ ( m ) ≡ 1 ( m o d m ) a^ { \phi(m) } \equiv 1\pmod m a ϕ ( m ) ≡ 1 ( mod m )
因此当 gcd ( a , m ) = 1 \gcd(a,m) =1 g cd( a , m ) = 1 时,存在一个最小正整数 n n n 使得 a n ≡ 1 ( m o d m ) a^ { n } \equiv 1\pmod m a n ≡ 1 ( mod m ) ,这个 n n n 称作 a a a 模 m m m 的阶,记作 δ m ( a ) \delta_m(a) δ m ( a ) 。
原根: 若 gcd ( a , m ) = 1 \gcd(a,m) =1 g cd( a , m ) = 1 ,且 δ m ( a ) = ϕ ( m ) \delta_m(a)=\phi(m) δ m ( a ) = ϕ ( m ) ,则称 a a a 为模 m m m 的原根。
阶的性质:
若 a n ≡ 1 ( m o d m ) a^n \equiv 1 \pmod m a n ≡ 1 ( mod m ) ,则 δ m ( a ) ∣ n \delta_m(a)|n δ m ( a ) ∣ n 。
设 gcd ( a , m ) = gcd ( b , m ) = 1 \gcd(a,m)=\gcd(b,m)=1 g cd( a , m ) = g cd( 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 g cd( δ m ( a ) , δ m ( b )) = 1 。
证明:
必要性:
由 a δ m ( a ) ≡ 1 ( m o d m ) a^ { \delta_m(a) } \equiv 1\pmod m a δ m ( a ) ≡ 1 ( mod m ) 和 b δ m ( b ) ≡ 1 ( m o d m ) b^ { \delta_m(b) } \equiv 1\pmod m b δ m ( b ) ≡ 1 ( mod m )
可知 ( a b ) l c m ( δ m ( a ) , δ m ( b ) ) ≡ 1 ( m o d m ) (ab)^ { { \rm lcm } (\delta_m(a),\delta_m(b)) } \equiv 1\pmod m ( ab ) lcm ( δ m ( a ) , δ m ( b )) ≡ 1 ( mod m ) ,
故 δ m ( a b ) ∣ l c m ( δ m ( a ) , δ m ( b ) ) \delta_m(ab)| { \rm lcm } (\delta_m(a),\delta_m(b)) δ m ( ab ) ∣ lcm ( δ m ( a ) , δ m ( b )) 。
又因为 δ m ( a b ) = δ m ( a ) δ m ( b ) \delta_m(ab)=\delta_m(a)\delta_m(b) δ m ( ab ) = δ m ( a ) δ m ( b ) ,
故 δ m ( a ) δ m ( b ) ∣ l c m ( δ m ( a ) , δ m ( b ) ) \delta_m(a)\delta_m(b)| { \rm lcm } (\delta_m(a),\delta_m(b)) δ m ( a ) δ m ( b ) ∣ lcm ( δ m ( a ) , δ m ( b )) ,
即 gcd ( δ m ( a ) , δ m ( b ) ) = 1 \gcd(\delta_m(a),\delta_m(b))=1 g cd( δ m ( a ) , δ m ( b )) = 1 。
充分性:
由 ( a b ) δ m ( a b ) ≡ 1 ( m o d m ) (ab)^ { \delta_m(ab) } \equiv 1\pmod m ( ab ) δ m ( ab ) ≡ 1 ( mod m ) 可知:
1 ≡ ( a b ) δ m ( a b ) δ m ( b ) ≡ a δ m ( a b ) δ m ( b ) × b δ m ( b ) δ m ( a b ) ≡ a δ m ( a b ) δ m ( b ) ( m o d m ) 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 1 ≡ ( ab ) δ m ( ab ) δ m ( b ) ≡ a δ m ( ab ) δ m ( b ) × b δ m ( b ) δ m ( ab ) ≡ a δ m ( ab ) δ m ( b ) ( mod m ) 。
故 δ m ( a ) ∣ δ m ( a b ) δ m ( b ) \delta_m(a)|\delta_m(ab)\delta_m(b) δ m ( a ) ∣ δ m ( ab ) δ m ( b ) 。
又因为 gcd ( δ m ( a ) , δ m ( b ) ) = 1 \gcd(\delta_m(a),\delta_m(b))=1 g cd( δ m ( a ) , δ m ( b )) = 1 ,
则 δ m ( a ) ∣ δ m ( a b ) \delta_m(a)|\delta_m(ab) δ m ( a ) ∣ δ m ( ab ) 。
同理,δ m ( b ) ∣ δ m ( a b ) \delta_m(b)|\delta_m(ab) δ m ( b ) ∣ δ m ( ab ) 。
又因为 gcd ( δ m ( a ) , δ m ( b ) ) = 1 \gcd(\delta_m(a),\delta_m(b))=1 g cd( δ m ( a ) , δ m ( b )) = 1 ,
故有 δ m ( a ) δ m ( b ) ∣ δ m ( a b ) \delta_m(a)\delta_m(b)|\delta_m(ab) δ m ( a ) δ m ( b ) ∣ δ m ( ab ) 。
另一方面, ( a b ) δ m ( a ) δ m ( b ) = ( a δ m ( a ) ) δ m ( b ) × ( b δ m ( b ) ) δ m ( a ) ≡ 1 ( m o d m ) (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 ( ab ) δ m ( a ) δ m ( b ) = ( a δ m ( a ) ) δ m ( b ) × ( b δ m ( b ) ) δ m ( a ) ≡ 1 ( mod m )
故 δ m ( a b ) ∣ δ m ( a ) δ m ( b ) \delta_m(ab)|\delta_m(a)\delta_m(b) δ m ( ab ) ∣ δ m ( a ) δ m ( b )
综上, δ m ( a b ) = δ m ( a ) δ m ( b ) \delta_m(ab)=\delta_m(a)\delta_m(b) δ m ( ab ) = δ m ( a ) δ m ( b )
3.若 gcd ( a , m ) = 1 \gcd(a,m)=1 g cd( a , m ) = 1 ,则 δ m ( a k ) = δ m ( a ) gcd ( δ m ( a ) , k ) \delta_m(a^k)=\frac { \delta_m(a) } { \gcd(\delta_m(a),k) } δ m ( a k ) = g c d ( δ m ( a ) , k ) δ m ( a )
证明:
a k δ m ( a k ) = ( a k ) δ m ( a k ) ≡ 1 ( m o d m ) a^ { k\delta_m(a^k) } =(a^k)^ { \delta_m(a^k) } \equiv 1\pmod m a k δ m ( a k ) = ( a k ) δ m ( a k ) ≡ 1 ( mod m )
⇒ δ m ( a ) ∣ k δ m ( a k ) \Rightarrow \delta_m(a)|k\delta_m(a^k) ⇒ δ m ( a ) ∣ k δ m ( a k )
⇒ δ m ( a ) gcd ( δ m ( a ) , k ) ∣ δ m ( a k ) \Rightarrow \frac { \delta_m(a) } { \gcd(\delta_m(a),k) } |\delta_m(a^k) ⇒ g c d ( δ m ( a ) , k ) δ m ( a ) ∣ δ m ( a k )
另一方面,
( a k ) δ m ( a ) gcd ( δ m ( a ) , k ) = ( a δ m ( a ) ) k gcd ( δ m ( a ) , k ) ≡ 1 ( m o d m ) (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 ( a k ) g c d ( δ m ( a ) , k ) δ m ( a ) = ( a δ m ( a ) ) g c d ( δ m ( a ) , k ) k ≡ 1 ( mod m )
⇒ δ m ( a k ) ∣ δ m ( a ) gcd ( δ m ( a ) , k ) \Rightarrow \delta_m(a^k)| { \frac { \delta_m(a) } { \gcd(\delta_m(a),k) } } ⇒ δ m ( a k ) ∣ g c d ( δ m ( a ) , k ) δ m ( a )
综上,有 δ m ( a k ) = δ m ( a ) gcd ( δ m ( a ) , k ) \delta_m(a^k)=\frac { \delta_m(a) } { \gcd(\delta_m(a),k) } δ m ( a k ) = g c d ( δ m ( a ) , k ) δ m ( a )
回到正题,我们来思考怎样的数有原根。
首先,易证 m = 1 , 2 , 4 m=1,2,4 m = 1 , 2 , 4 时有原根。
结论一:奇质数有原根
结论二:对于奇质数 p p p , α ∈ N ∗ \alpha\in \mathbb { N } ^* α ∈ N ∗ , p α p^\alpha p α 有原根。
结论三:对于奇质数 p p p , α ∈ N ∗ \alpha\in\mathbb { N } ^* α ∈ N ∗ , 2 p α 2p^\alpha 2 p α 有原根。
结论四:对于 m ∈ { 1 , 2 , 4 } m \in \{1,2,4\} m ∈ { 1 , 2 , 4 } 且不存在奇质数 p p p 及 α ∈ N ∗ \alpha\in\mathbb { N } ^* α ∈ N ∗ 使得 m ∈ { p α , 2 p α } m\in\{p^\alpha,2p^\alpha\} m ∈ { p α , 2 p α } ,模 m m m 的原根不存在。
综上,仅 1 , 2 , 4 , p α , 2 p α 1,2,4,p^\alpha,2p^\alpha 1 , 2 , 4 , p α , 2 p α ,其中 p p p 为奇质数, α ∈ N ∗ \alpha\in\mathbb { N } ^* α ∈ N ∗ 。
如何求一个数 n n n 的所有原根?
首先找到 n n n 的最小原根,记为 g g g ,则 n n n 的所有原根均可由 g g g 乘方得到。
具体的,若 n n n 有原根,必有 ϕ ( ϕ ( n ) ) \phi(\phi(n)) ϕ ( ϕ ( n )) 个原根,每个都形如 g k m o d n g^k\mod n g k mod n 的形式,要求满足 gcd ( k , ϕ ( n ) ) = 1 \gcd(k,\phi(n))=1 g cd( k , ϕ ( n )) = 1 。
于是,我们在求得 n n n 的最小原根 g g g 后便可 在 O ( ϕ ( n ) log ϕ ( n ) ) O(\phi(n)\log\phi(n)) O ( ϕ ( n ) log ϕ ( n )) 的时间内求得 n n n 的所有原根。
如何求得 n n n 的最小原根 g g g ?
枚举即可。
王元于 1959 年证明了若 n n n 有原根,其最小原根是不多 n 0.25 n^ { 0.25 } n 0.25 级别的。
事实上,由大量试验数据可以发现,对于足够大的 n n n ,其最小正原根的大小不是多项式级别的。
——百度百科
根据这个结论,我们可以从小到大枚举。由原根的定义,若 g g g 为模 n n n 的原根,则对于 ϕ ( n ) \phi(n) ϕ ( n ) 的任意素因数 p p p ,必有:
g ϕ ( n ) / p ≢ 1 ( m o d n ) g^ { \phi(n)/p } \not\equiv 1\pmod n g ϕ ( n ) / p ≡ 1 ( mod n )
并且,满足如上条件的 g g g 必为 n n n 的原根
我们可以预处理出 ϕ ( n ) \phi(n) ϕ ( n ) 的所有质因数,快速幂来判断。
problem link: \texttt { problem link: } problem link:
Luogu P6091 【模板】原根
code \texttt { code } 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) ϕ ( n ) 质因数的倍数后,速度提升了数倍不止。
修改后的代码如下:
code \texttt { code } 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. } that’s all.