1 模m原根的定义
1.1符号说明:
Z
m
∗
Z_m^*
Zm∗:代表满足
1
<
=
i
<
=
m
−
1
,
(
i
,
m
)
=
1
1<=i<=m-1,(i,m)=1
1<=i<=m−1,(i,m)=1的数字
i
i
i组成的集合
o
r
d
m
(
a
)
ord_m(a)
ordm(a):代表
a
(
m
o
d
m
)
a(mod m)
a(modm)在
Z
m
∗
Z_m^*
Zm∗中的阶,即使得
a
d
≡
1
a^d \equiv 1
ad≡1的最小正整数
d
d
d。
φ
(
m
)
\varphi(m)
φ(m):数
m
m
m简化剩余系中的元素个数,即集合
Z
m
∗
Z_m^*
Zm∗中的元素个数。
1.2定义
若 o r d m ( a ) = φ ( m ) ord_m(a)= \varphi(m) ordm(a)=φ(m),则称 a a a为模 m m m的原根。即在 Z m ∗ Z_m^* Zm∗的乘法上的生成元。
2 素数求原根
2.1 理论基础
φ
(
m
)
=
m
−
1
=
p
i
a
∗
p
j
b
.
.
.
p
k
c
(
p
i
,
p
j
.
.
.
p
k
\varphi(m)=m-1=p_i^a*p_j^b...p_k^c(p_i,p_j...p_k
φ(m)=m−1=pia∗pjb...pkc(pi,pj...pk为素数
)
)
)。对于任意一个
p
i
p_i
pi,都满足
a
φ
(
m
)
/
p
i
≢
1
(
m
o
d
m
)
a^{\varphi(m)/p_i} \not\equiv 1 \pmod m
aφ(m)/pi≡1(modm),则
a
a
a为模
m
m
m的原根。
证明: 根据拉个格朗日定理,对于任意一个
a
a
a
∈
\in
∈
Z
m
∗
Z_m^*
Zm∗,有
o
r
d
m
(
a
)
ord_m(a)
ordm(a) |
φ
(
m
)
\varphi(m)
φ(m),即当且仅当d|
φ
(
m
)
\varphi(m)
φ(m)时,才会使得
a
d
≡
1
(
m
o
d
m
)
a^d \equiv 1 \pmod m
ad≡1(modm)。
根据模m原根的定义,要求
o
r
d
m
(
a
)
=
φ
(
m
)
ord_m(a)= \varphi(m)
ordm(a)=φ(m),即当且仅当
d
=
φ
(
m
)
d=\varphi(m)
d=φ(m)时,才有
a
d
≡
1
(
m
o
d
m
)
a^d \equiv 1 \pmod m
ad≡1(modm)。
进一步地推导可知, d ≠ φ ( m ) d\not =\varphi(m) d=φ(m)且d| φ ( m ) \varphi(m) φ(m)时,若对于所有d都满足 a d ≢ 1 ( m o d m ) a^d \not\equiv 1 \pmod m ad≡1(modm),则a为原根 (条件一) 。
对于任意一个 d d d,因为 d ≠ φ ( m ) d\not =\varphi(m) d=φ(m)且d| φ ( m ) \varphi(m) φ(m),因此存在i,使得 d ∣ d | d∣ φ ( m ) / p i \varphi(m) / p_i φ(m)/pi,由于 a d ≢ 1 ( m o d m ) a^d \not\equiv 1 \pmod m ad≡1(modm),因此可以推导出 a φ ( m ) / p i ≢ 1 ( m o d m ) a^{\varphi(m) / p_i} \not\equiv 1 \pmod m aφ(m)/pi≡1(modm)。
因此,若对于所有 p i p_i pi,满足 a φ ( m ) / p i ≢ 1 ( m o d m ) a^{\varphi(m) / p_i} \not\equiv 1 \pmod m aφ(m)/pi≡1(modm),则a为原根 (条件二) 。
拉格朗日定理:在有限群 < G , ∗ > <G,*> <G,∗>中,每个元素的周期是# G G G(集合G的元素的个数)的因子。
2.2 算法流程
算法的整体思路是根据2.1节所述的条件二来制定的。其具体的流程如下:
step1: 使用素数线性筛法,找出
1
1
1到
m
m
m中的所有素数。
step2: 确定m的质因子集合{
p
i
p_i
pi}。
step3: 遍历
2
2
2到
m
m
m中的所有数,假设当前处理的数为a。遍历m的所有质因子,若都满足
a
φ
(
m
)
/
p
i
a^{ \varphi(m) / p_i}
aφ(m)/pi
≢
\not\equiv
≡ 1
(
m
o
d
m
)
\pmod m
(modm),则a为模m的原根。
2.3代码实现
#include<iostream>
using namespace std;
#define NUM 998244380
bool is_prime[NUM] = { 0 };
#define NMAX 400000
long long prime[NMAX];
long long prime_num = 0;
long long m;//待处理的素数
long long p_factor[NMAX];
long long p_factor_num = 0;
void get_prime(long long n) {
for (long long m = 2; m < n; m++) {
if (m == 98244379)
{
cout << m << endl;
}
if (m == 298244379)
{
cout << m << endl;
}
//数m没有被筛选过
if (!is_prime[m]) {
if (prime_num > NMAX) {
cout << "越界" << endl;
}
prime[prime_num++] = m;
}
//筛选掉以prime[i]为最小素因子的合数m*prime[i]
for (long long i = 0; i < prime_num; i++) {
if ((m * prime[i]) < n) is_prime[m * prime[i]] = 1;
if (m % prime[i] == 0) break;//m*prime[j](j>i)的最小素因子为m的最小素因子prime[i]而非prime[j],因此跳出循环
}
}
//打印输出
/*for (int i = 0; i < prime_num; i++) {
cout << prime[i] << " ";
}
cout << endl;*/
}
void get_p_factor(long long m) {
p_factor_num = 0;
for (long long i = 0; i < prime_num; i++) {
if ((m - 1) % prime[i] == 0) p_factor[p_factor_num++] = prime[i];
}
}
long long ksm(long long a, long long d, long long mod) {
long long ret = 1;
while (d) {
if ((d & 1) == 1) ret = (ret * a) % mod;//按位与为&,而&&代表的是条件与
a = (a * a) % mod;
d >>= 1;
}
return ret;
}
int judge(long long a) {
for (long long i = 0; i < p_factor_num; i++) {
if (ksm(a, (m - 1) / p_factor[i], m) == 1) {
return 0;
}
}
return 1;
}
int main() {
//1.获取所有的素数
get_prime(1000);
while (cin >> m) {
//获取m的所有质因子
get_p_factor(m);
//遍历2...m-1进行判断
for (long long a = 2; a < m; a++) {
if (judge(a)) {
cout << a << endl;
break;
}
}
}
}