什么是阶
若 (a,m)=1,则使 al≡1 (mod m) 的最小 l,称之为 a 关于模 m 的阶,记为 ordma
举个例子:
比如要求 2 模 7 的阶,可以一一列举 2 的幂次:
21≡2 (mod 7)
22≡4 (mod 7)
23≡1 (mod 7)
24≡2 (mod 7)
25≡4 (mod 7)
26≡1 (mod 7)
于是可以看出, 2 模 7 的阶为 3。
阶的性质
- 根据欧拉定理,我们有 aφ(m)≡1 (mod m)。
于是 ordma∣φ(m)。 - 当 ax≡1 (mod m) 时, ordma∣x
- 阶相当于最小循环节,即 ax≡ax+ordma (mod m)。因此 a1,a2,....,aordma 构成模 m 意义下的一个既约剩余系。从上述例子也可以看出,如 21≡24 (mod 7)。
- ordmat=gcd(ordma,t)ordma,证明如下:
当 at∗l≡1 (mod m)时, ordma∣t∗l,t∣t∗l,因此 l 最小时, t∗l 应为 lcm(t,ordma),可以推得 l=gcd(ordma,t)ordma
原根
当 ordma=φ(m) 时,称 a 为关于 m 的原根。
也就是说,使得 ax≡1 (mod m) 成立的最小 x 为 φ(m)。
举个例子:
7 的原根是 3,因为:
31≡3 (mod 7)
32≡2 (mod 7)
33≡6 (mod 7)
34≡4 (mod 7)
35≡5 (mod 7)
36≡1 (mod 7)
于是只有 3φ(7)≡1 (mod 7)。
再举一个非素数的例子,比如 5 是 18 的一个原根。 φ(18)=6
51≡5 (mod 18)
52≡7 (mod 18)
53≡17 (mod 18)
54≡13 (mod 18)
55≡11 (mod 18)
56≡1 (mod 18)
于是最小的 x 使得 5x≡1 (mod 18) 是 φ(18),也就是 6。
原根的性质
- 当 g 为原根时, g1,g2,....,gφ(m) 构成模 m 意义下的既约剩余系。
从上面 5 是 18 的原根可以看到, 51 到 56 模 18 之后恰好是 6 个和 18 互质的数。
具体证明也不难,因为 g 和 m 互质,因此 gx 和 m 互质,总共有 φ(m) 个 x,于是生成了 φ(m) 个与 m 互质的数,也就是 m 的既约剩余系。 - 如果 m 有原根,那么 m 总共有 φ(φ(m)) 个原根。
证明一下:
设 g 为 m 的一个原根。那么所有原根肯定存在于 g1,g2,...,gφ(m) 中,因为原根肯定与 m 互质。那么我们考虑 g 的幂次。
当 ordmgt=φ(m) 时, gt 为 m 的原根,我们来看看这样的 t 有多少个。由阶的性质 4, ordmgt=gcd(φ(m),t)φ(m)=φ(m) 时, gcd(φ(m),t)=1,也就是说,这样的 t 有 φ(φ(m)) 个。 - 从上面的证明我们可以看到,若 g 为 m 的一个原根,则所有原根集合为 {gs∣1≤s≤φ(m),(s,φ(m)=1)}
原根的作用
原根的最大意义在于,它可以映射 m 的既约剩余系,而且每个元素一一对应。
比如我们在求 xa≡b (mod m) ( m 是质数)时,我们可以设 gt≡x (mod m),其中 g 为 m 的原根,不难证明这样的 t 一定存在。且一个 t 与一个 x 一一对应。
那么即是求 (gt)a≡(ga)t≡b (mod m) 的 t。因为 ga 已知,所以用 BSGS 求 t 即可。
原根还在 NTT 中有着重要的作用。
于是学习原根其实是在为学 NTT 做铺垫。
原根存在的条件
当 m=2,4,pk,2∗pk ( p 为奇素数)时, m 的原根存在。其余情况不存在原根。
证明的话,我不会证。。
如何求原根
因为原根密度很大,大约是 n0.25,所以可以考虑暴力枚举找到一个原根。
当枚举到 a 时,如何快速判断 a 是不是原根呢?注意 a,m 要互质。
a 不是原根的条件是,存在 l<φ(m),使得 al≡1 (mod m)。
设 φ(m)=∏piki
由于 l 是 φ(m) 的约数,所以我们预处理 φ(m) 的素因子,然后枚举素因子 pi,判断是否 apiφ(m)≡1 (mod m) 即可。
单次判断复杂度是 O(log2n) 的。
这里有一道模板题
代码如下
#include <bits/stdc++.h>
#include<ext/pb_ds/hash_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
LL z = 1;
int read(){
int x, f = 1;
char ch;
while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
x = ch - '0';
while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48;
return x * f;
}
int ksm(int a, int b, int p){
int s = 1;
while(b){
if(b & 1) s = z * s * a % p;
a = z * a * a % p;
b >>= 1;
}
return s;
}
int phi(int x){
int ret = x;
for(int i = 2; i <= x / i; i++){
if(x % i == 0){
ret = ret / i * (i - 1);
while(x % i == 0) x /= i;
}
}
if(x > 1) ret = ret / x * (x - 1);
return ret;
}
vector<int> d;
void get(int x){
for(int i = 2; i <= x / i; i++){
if(x % i == 0){
d.push_back(i);
while(x % i == 0) x /= i;
}
}
if(x > 1) d.push_back(x);
}
int main(){
int i, j, flag, n, p, m;
p = read(); m = phi(p);
get(m);
for(i = 2; i <= p; i++){
flag = 0;
for(auto x: d){
if(ksm(i, m / x, p) == 1){
flag = 1;
break;
}
}
if(!flag){
printf("%d", i);
return 0;
}
}
return 0;
}