原根
1 阶
1.1 定义
由欧拉定理可知,对于
因此满足同余式
1.2 性质
只要扯到数学性质和证明这部分内容就会极其恶臭。
性质 1:
证明:考虑反证。若存在
性质 2:
若
证明:设
所以
据此性质可有以下推论:若
性质 3:
设
的充要条件是
证明:
-
必要性:
由
可得 。根据性质二可得
,故 ,因此 。 -
充分性:
由
可得 。故
。由于
,所以 ,同理 。于是 。另一方面有
。所以 。于是
。
性质 4:
设
证明:
由
由
于是
2 原根
2.1 定义
设
当
2.2 原根判定定理
设
证明:必要性显然,如果存在则说明
充分性考虑反证,假设存在一个
由于
2.3 原根个数
若一个数
证明:
若
所以如果
2.4 原根存在定理
一个数
这个定理可以帮助我们用于判定一个数是否有原根,其证明较为复杂,在此不作详细叙述,可以看OI Wiki上的证明。
2.5 求原根
有了上面三个定理,找原根其实并不困难。我们首先判断该数字是否有原根,如果有暴力枚举最小原根
例题:【模板】原根,代码如下:
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int Maxn = 1e6 + 5;
const int Inf = 2e9;
int T;
int n, d;
int prim[Maxn], tot, phi[Maxn];
int vis[Maxn];
int rt[Maxn];
void init(int N) {
phi[1] = 1;
for(int i = 2; i <= N; i++) {
if(!vis[i]) {
prim[++tot] = i;
phi[i] = i - 1;
}
for(int j = 1, x; (x = prim[j] * i) <= N; j++) {
vis[x] = 1;
if(i % prim[j] == 0) {
phi[x] = phi[i] * prim[j];
break;
}
phi[x] = phi[i] * (prim[j] - 1);
}
}
rt[2] = rt[4] = 1;
for(int i = 2; i <= tot; i++) {
for(int j = prim[i]; j <= N; j *= prim[i]) rt[j] = 1;
for(int j = 2 * prim[i]; j <= N; j *= prim[i]) rt[j] = 1;
}
}
int fac[Maxn], cnt;
void divd(int x) {
cnt = 0;
for(int i = 1; prim[i] <= x / prim[i]; i++) {
if(x % prim[i] == 0) {
fac[++cnt] = prim[i];
while(x % prim[i] == 0) x /= prim[i];
}
}
if(x > 1) fac[++cnt] = x;
}
int qpow(int a, int b, int p) {
int res = 1;
while(b) {
if(b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
int check(int g, int m) {//判断是否是原根
if(qpow(g, phi[m], m) != 1) return 0;
for(int i = 1; i <= cnt; i++) {
if(qpow(g, phi[m] / fac[i], m) == 1) return 0;
}
return 1;
}
int findrt(int m) {//找最小原根
for(int i = 1; i < m; i++) {
if(check(i, m)) return i;
}
}
int ans[Maxn], ret;
void getrt(int g, int m) {//找出所有原根
int res = 1;
ret = 0;
for(int i = 1; i <= phi[m]; i++) {
res = res * g % m;
if(__gcd(phi[m], i) == 1) {
ans[++ret] = res;
}
}
}
void solve() {
cin >> n >> d;
if(rt[n]) {//判断是否有原根
divd(phi[n]);//分解质因数
getrt(findrt(n), n);//找出原根
sort(ans + 1, ans + ret + 1);
cout << ret << '\n';
for(int i = d; i <= ret; i += d) {
cout << ans[i] << " ";
}
cout << '\n';
}
else {
cout << "0\n\n";
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
init(1e6);
while(T--) solve();
return 0;
}
3 指标
3.1 定义
指标,也被称作离散对数,实际上就是模意义下的对数运算。设
称
指标的求法很简单,既然是离散对数,那么直接用 BSGS 算法即可。
3.2 性质
指标的性质与普通对数类似,下面列举几条。注意下式中
性质 1:
若
该性质可以直接由阶的性质 2 中的推论得到。
性质 2:
证明:
性质 3:
证明:对
4 例题
例 1 [BZOJ1420] Discrete Root
题意: 给定
既然
例 2 [BZOJ2219] 数论之神
题意:给定
上面一道题其实只是为了引出这道题的。发现此时模数变为
不过我们可以对
我们对于每一个方程可以求出一个
那么现在的问题就是求
-
时:此时
,所以方程化为 。设 ,代入有 。于是有 ,解得 。那么实际上只需要让 即可,所以 取值有 种。 -
时:此时就与上一道题是一样的了,先求出指标
,如果 ,那么取值就有 种。 -
时:令
,那么会有 。显然此时如果有解,那么 中也必然有 。于是根据同余方程的消去律,可以得到 ,第一项其实也就是 。那么此时
就等于 了,回到了情况 2。但是注意到此时求出来的 的取值范围是 的,但是我们要求的范围是 的,所以还需要给求出来的结果乘上 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律