什么是阶
若
,则使
的最小
,称之为
关于模
的阶,记为
举个例子:
比如要求
模
的阶,可以一一列举
的幂次:
于是可以看出,
模
的阶为
。
阶的性质
- 根据欧拉定理,我们有
。
于是 。 - 当 时,
- 阶相当于最小循环节,即 。因此 构成模 意义下的一个既约剩余系。从上述例子也可以看出,如 。
-
,证明如下:
当 时, ,因此 最小时, 应为 ,可以推得
原根
当
时,称
为关于
的原根。
也就是说,使得
成立的最小
为
。
举个例子:
的原根是
,因为:
于是只有
。
再举一个非素数的例子,比如
是
的一个原根。
于是最小的
使得
是
,也就是
。
原根的性质
- 当
为原根时,
构成模
意义下的既约剩余系。
从上面 是 的原根可以看到, 到 模 之后恰好是 个和 互质的数。
具体证明也不难,因为 和 互质,因此 和 互质,总共有 个 ,于是生成了 个与 互质的数,也就是 的既约剩余系。 - 如果
有原根,那么
总共有
个原根。
证明一下:
设 为 的一个原根。那么所有原根肯定存在于 中,因为原根肯定与 互质。那么我们考虑 的幂次。
当 时, 为 的原根,我们来看看这样的 有多少个。由阶的性质 , 时, ,也就是说,这样的 有 个。 - 从上面的证明我们可以看到,若 为 的一个原根,则所有原根集合为
原根的作用
原根的最大意义在于,它可以映射
的既约剩余系,而且每个元素一一对应。
比如我们在求
(
是质数)时,我们可以设
,其中
为
的原根,不难证明这样的
一定存在。且一个
与一个
一一对应。
那么即是求
的
。因为
已知,所以用
求
即可。
原根还在
中有着重要的作用。
于是学习原根其实是在为学 NTT 做铺垫。
原根存在的条件
当
(
为奇素数)时,
的原根存在。其余情况不存在原根。
证明的话,我不会证。。
如何求原根
因为原根密度很大,大约是
,所以可以考虑暴力枚举找到一个原根。
当枚举到
时,如何快速判断
是不是原根呢?注意
要互质。
不是原根的条件是,存在
,使得
。
设
由于
是
的约数,所以我们预处理
的素因子,然后枚举素因子
,判断是否
即可。
单次判断复杂度是
的。
代码如下
#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;
}