若 ( a , m ) = 1 (a,m)=1 (a,m)=1,则使 a l ≡ 1 ( m o d m ) a^l\equiv1~(mod~m) al≡1 (mod m) 的最小 l l l,称之为 a a a 关于模 m m m 的阶,记为 o r d m a ord_m a ordma
举个例子:
比如要求 2 2 2 模 7 7 7 的阶,可以一一列举 2 2 2 的幂次:
2 1 ≡ 2 ( m o d 7 ) 2^1 \equiv 2~(mod~7) 21≡2 (mod 7)
2 2 ≡ 4 ( m o d 7 ) 2^2 \equiv 4~(mod~7) 22≡4 (mod 7)
2 3 ≡ 1 ( m o d 7 ) 2^3 \equiv 1~(mod~7) 23≡1 (mod 7)
2 4 ≡ 2 ( m o d 7 ) 2^4 \equiv 2~(mod~7) 24≡2 (mod 7)
2 5 ≡ 4 ( m o d 7 ) 2^5 \equiv 4~(mod~7) 25≡4 (mod 7)
2 6 ≡ 1 ( m o d 7 ) 2^6 \equiv 1~(mod~7) 26≡1 (mod 7)
于是可以看出, 2 2 2 模 7 7 7 的阶为 3 3 3。
当 o r d m a = φ ( m ) ord_m a=\varphi(m) ordma=φ(m) 时,称 a a a 为关于 m m m 的原根。
也就是说,使得 a x ≡ 1 ( m o d m ) a^x\equiv 1~(mod~m) ax≡1 (mod m) 成立的最小 x x x 为 φ ( m ) \varphi(m) φ(m)。
举个例子:
7 7 7 的原根是 3 3 3,因为:
3 1 ≡ 3 ( m o d 7 ) 3^1\equiv3~(mod~7) 31≡3 (mod 7)
3 2 ≡ 2 ( m o d 7 ) 3^2\equiv2~(mod~7) 32≡2 (mod 7)
3 3 ≡ 6 ( m o d 7 ) 3^3\equiv6~(mod~7) 33≡6 (mod 7)
3 4 ≡ 4 ( m o d 7 ) 3^4\equiv4~(mod~7) 34≡4 (mod 7)
3 5 ≡ 5 ( m o d 7 ) 3^5\equiv5~(mod~7) 35≡5 (mod 7)
3 6 ≡ 1 ( m o d 7 ) 3^6\equiv1~(mod~7) 36≡1 (mod 7)
于是只有 3 φ ( 7 ) ≡ 1 ( m o d 7 ) 3^{\varphi(7)}\equiv 1~(mod~7) 3φ(7)≡1 (mod 7)。
再举一个非素数的例子,比如 5 5 5 是 18 18 18 的一个原根。 φ ( 18 ) = 6 \varphi(18)=6 φ(18)=6
5 1 ≡ 5 ( m o d 18 ) 5^1\equiv 5~(mod~18) 51≡5 (mod 18)
5 2 ≡ 7 ( m o d 18 ) 5^2\equiv 7~(mod~18) 52≡7 (mod 18)
5 3 ≡ 17 ( m o d 18 ) 5^3\equiv 17~(mod~18) 53≡17 (mod 18)
5 4 ≡ 13 ( m o d 18 ) 5^4\equiv 13~(mod~18) 54≡13 (mod 18)
5 5 ≡ 11 ( m o d 18 ) 5^5\equiv 11~(mod~18) 55≡11 (mod 18)
5 6 ≡ 1 ( m o d 18 ) 5^6\equiv 1~(mod~18) 56≡1 (mod 18)
于是最小的 x x x 使得 5 x ≡ 1 ( m o d 18 ) 5^x\equiv 1~(mod~18) 5x≡1 (mod 18) 是 φ ( 18 ) \varphi(18) φ(18),也就是 6 6 6。
原根的最大意义在于,它可以映射 m m m 的既约剩余系,而且每个元素一一对应。
比如我们在求 x a ≡ b ( m o d m ) x^a\equiv b~(mod~m) xa≡b (mod m) ( m m m 是质数)时,我们可以设 g t ≡ x ( m o d m ) g^t\equiv x~(mod~m) gt≡x (mod m),其中 g g g 为 m m m 的原根,不难证明这样的 t t t 一定存在。且一个 t t t 与一个 x x x 一一对应。
那么即是求 ( g t ) a ≡ ( g a ) t ≡ b ( m o d m ) (g^t)^a\equiv (g^{a})^t\equiv b~(mod~m) (gt)a≡(ga)t≡b (mod m) 的 t t t。因为 g a g^a ga 已知,所以用 B S G S BSGS BSGS 求 t t t 即可。
原根还在 N T T NTT NTT 中有着重要的作用。
于是学习原根其实是在为学 NTT 做铺垫。
当 m = 2 , 4 , p k , 2 ∗ p k m=2,4,p^k,2*p^k m=2,4,pk,2∗pk ( p p p 为奇素数)时, m m m 的原根存在。其余情况不存在原根。
证明的话,我不会证。。
因为原根密度很大,大约是 n 0.25 n^{0.25} n0.25,所以可以考虑暴力枚举找到一个原根。
当枚举到 a a a 时,如何快速判断 a a a 是不是原根呢?注意 a , m a,m a,m 要互质。
a a a 不是原根的条件是,存在 l < φ ( m ) l<\varphi(m) l<φ(m),使得 a l ≡ 1 ( m o d m ) a^l\equiv 1~(mod~m) al≡1 (mod m)。
设 φ ( m ) = ∏ p i k i \varphi(m)=\prod p_i^{k_i} φ(m)=∏piki
由于 l l l 是 φ ( m ) \varphi(m) φ(m) 的约数,所以我们预处理 φ ( m ) \varphi(m) φ(m) 的素因子,然后枚举素因子 p i p_i pi,判断是否 a φ ( m ) p i ≡ 1 ( m o d m ) a^{\frac{\varphi(m)}{p_i}} \equiv 1~(mod~m) apiφ(m)≡1 (mod m) 即可。
单次判断复杂度是 O ( l o g 2 n ) O(log^2n) O(log2n) 的。
这里有一道模板题
#include
#include
#include
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;
}