数论笔记-原根
原根
阶
阶的定义与基本性质
定义 设 且 ,那么满足 的最小正整数 称为 模 的阶,记作 或 。
阶是群论的概念, 模 的阶即 在模 缩系下生成的乘法群大小。
性质1 模 互不相同。
性质2 正整数 满足 ,当且仅当 。
性质3 一定存在,且 。
性质4 设 ,有 。
性质5 设 ,有 。
性质1的证明:
假设存在不同的两个数 ,满足 ,那么有 ,其中 ,与定义相悖,因此原命题成立。
性质2的证明:
必要性:
使用带余数除法, 除以 得 ,其中 。
那么, ,根据阶的定义, 或 。
因此 ,即 。
充分性:
显然。
性质3的证明:
根据欧拉定理,有 ,又根据性质2,阶一定存在,且是 的因数。
性质4的证明:
必要性:
由 ,可得 ,因此 。又 ,因此 ,所以 。
充分性:
由 ,得 ,因此 ,所以 ,又 ,根据整除的基本性质4,有 。
同理有 。
又因为 ,所以 。
同时 ,所以 。
综上,根据整除的基本性质7, 。
性质5的证明:
由 ,因此 ,所以 。
同时 ,所以 。
综上,根据整除的基本性质7, 。
原根
原根的定义与基本性质
定义 设 且 ,若 ,则称 为模 的原根。
原根即群论中的生成元。“根”表示方程 的一个解,“原”表示能生成模 缩系的所有元素。因此,模数 存在原根,也代表模 缩系的乘法群是一个循环群。
性质1 若 存在原根 ,那么 构成模 的简化剩余系。
性质2 若 存在原根,那么对于任意 的因子 ,模 的 阶元素个数为 。
-
推论1(性质2的推论,原根个数) 若 存在原根,那么原根的个数为 。
-
推论2(性质2的推论) 若 存在原根,那么对于任意 的因子 , 的解恰好有 个。
性质3 素数 的最小原根 ,满足 。
性质4 若素数 的原根是 ,那么 和 中必有一个是 的原根, 和 中的奇数是 的原根。
性质1的证明:
由于 ,因此 互不相同且都与 互质,因此其构成模 的简化剩余系。
性质2的证明:
设 ,那么 。
设正整数 满足 ,根据阶的性质5,当且仅当 时, ,这样的 有 个。
而根据性质1, 一定模 互不相同,所以模 的 阶的元素至少有 个。
当 时, ,与 时的情况重复。
综上模 的 阶元素个数为 。
推论1的证明:
根据性质2,模 的 阶元素共有 个。
推论2的证明:
根据性质2,模 的 阶元素有 个。
同时,仅对于满足 的正整数 , 阶元素都是方程的解,显然这些元素各不相同。
因此,满足方程解的个数恰好为 个。
性质3的证明:
出自论文,详见 OI-wiki 。
性质4的证明:
不会qwq
原根判定性定理
定理1(原根判定性定理) 是模 的原根,当且仅当对于任意 的素因数 ,满足 。
定理1的证明:
必要性:显然。
充分性:
假设 不是模 的原根,即存在 满足 。
根据裴蜀定理,存在一组整数 使得 。
此时,有 ,而 且 。
因此,存在 的素因数 ,使得 ,所以 ,矛盾。
因此 是模 的原根。
原根存在性定理
定理1(原根存在性定理) 模数 的原根存在,当且仅当 ,其中 为奇素数,。
定理1的证明:
可参考 OI-wiki 。
原根的求法
枚举法(最小原根)
求 的最小原根大致分为以下几步:
- 对 质因数分解,根据原根存在性定理判断 是否有原根,复杂度 。
- 求 及其质因数集合,复杂度 。
- 枚举 中与 互质的数,根据原根判定性定理判定,得到最小原根 ,复杂度 。
第三步根据性质3的估计得到。
前两步可以换成筛法预处理,复杂度更大但常数小,时间上差不多,按需求改即可。
代码在求所有原根中给出。
时间复杂度
空间复杂度
枚举法(所有原根)
先求出最小原根,根据性质2的证明,枚举 中与 互质的数, 即为原根。
可以换成 bitset
进行 递推,常数会小很多。
时间复杂度
空间复杂度
int one_euler(int n) { int ans = n; for (int i = 2;i * i <= n;i++) { if (!(n % i)) { ans = ans / i * (i - 1); while (!(n % i)) n /= i; } } if (n > 1) ans = ans / n * (n - 1); return ans; } void get_pfactor(int n, vector<int> &pfactor) { for (int i = 2;i * i <= n;i++) { if (!(n % i)) { pfactor.push_back(i); while (!(n % i)) n /= i; } } if (n > 1) pfactor.push_back(n); } int qpow(int a, ll k, int P) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; } bool exist_proot(int n) { if (n == 2 || n == 4) return true; vector<int> pfactor; if (n & 1) get_pfactor(n, pfactor); else get_pfactor(n / 2, pfactor); return pfactor.size() == 1; } int min_proot(int n) { if (!exist_proot(n)) return 0; int phi_n = one_euler(n); vector<int> pfactor; get_pfactor(phi_n, pfactor); for (int i = 1;i <= n;i++) { if (gcd(i, n) != 1) continue; bool ok = 1; for (auto j : pfactor) ok &= qpow(i, phi_n / j, n) != 1; if (ok) return i; } return 0; } void get_proot(int n, vector<int> &proot) { int g = min_proot(n); if (!g) return; int phi_n = one_euler(n); for (int i = 1;i <= phi_n;i++) { if (gcd(i, phi_n) != 1) continue; proot.push_back(qpow(g, i, n)); } }
指标
指标的定义与基本性质
定义 设 为模 的原根,存在唯一整数 满足 ,称这个 是 模 关于 的指标(离散对数、指数),记作 。
离散对数和实数对数运算规则几乎没有区别,但注意离散对数建立在模 的简化剩余系,域内元素都是可逆的。
设正整数 ,满足 , 是模 的原根。
性质1(唯一性) 。
性质2(加法) 。
性质3(减法) 。
性质4(乘法) 对于任意整数 , 。
性质5(除法) 若整数 满足 ,那么 。
性质6(逆元) 若 也是模 的原根,则 模 的逆元是 。
性质7(换底公式) 若 也是模 的原根,那么 。
性质1的证明:
性质2的证明:
显然, 。
根据性质1得 。
性质3的证明:
显然 有模 的逆元,那么 。
根据性质1得 。
性质4的证明:
根据性质1,且 具有模 的逆元,显然得证。
性质5的证明:
显然 具有模 的逆元。
根据性质4和定义, 。
性质6的证明:
根据性质4和定义,得 ,符合逆元定义。
性质7的证明:
根据性质6, 模 的逆元存在。
设 ,那么 。
根据定义 。
指标的求法
BSGS算法
BSGS算法求解该类问题:给定 ,满足 , 求 的最小非负整数解 。
根据阶的相关性质,我们知道 的次方的最小循环节是 。而循环节最坏情况为 ,为了方便,我们直接在 中找最小解即可,不影响复杂度。
我们考虑用分块代替直接枚举,设块大小为 ,令 ,其中 ,那么 。显然 ,可以覆盖除了 的所有情况,我们一开始特判 即可。
此时,原方程变为 ,进一步变形 。此时,我们枚举 ,只要找到 使得方程成立,即可得到一个解。注意到 ,可以将 的映射存下来,每次枚举 的时候查询等于 对应的 即可。
需要注意的是,我们要求最小的非负整数解,那么在保证 尽可能小之后,还要保证 尽可能大,因此在存映射的时候,遇到 值相同的,我们存 较大的。
使用前确保 。
时间复杂度
空间复杂度
int BSGS(int a, int b, int P) { if (1 % P == b % P) return 0; unordered_map<int, int> ump; int B = sqrt(P) + 1; int aB = 1; for (int i = 0;i <= B - 1;i++) { ump[1LL * aB * b % P] = i; aB = 1LL * aB * a % P; } for (int i = 1, val = aB;i <= B;i++) { if (ump.count(val)) return 1LL * i * B - ump[val]; val = 1LL * val * aB % P; } return -1; }
扩展BSGS算法
考虑方程 ,其中 不一定为 ,求解最小非负整数解。
如果 ,直接使用BSGS算法即可。
否则,设 ,那么 模 的乘法群中都是 的倍数,因此 要满足 才有解,否则无解。
现在,根据同余基本性质的同除性,方程等价于 。
显然 存在逆元,因此我们得到新的方程 ,对这个方程递归做上面步骤即可,每层递归要将答案加 。这里加 可以证明不影响解是最小的。
时间复杂度
空间复杂度
int exgcd(int a, int b, int &x, int &y) { if (!b) { x = 1, y = 0; return a; } int d = exgcd(b, a % b, x, y); x -= (a / b) * y, swap(x, y); return d; } int inv(int a, int P) { int x, y; exgcd(a, P, x, y); return (x % P + P) % P; } int BSGS(int a, int b, int P) { if (1 % P == b) return 0; unordered_map<int, int> ump; int B = sqrt(P) + 1; int aB = 1; for (int i = 0;i <= B - 1;i++) { ump[1LL * aB * b % P] = i; aB = 1LL * aB * a % P; } for (int i = 1, val = aB;i <= B;i++) { if (ump.count(val)) return 1LL * i * B - ump[val]; val = 1LL * val * aB % P; } return -1; } int exBSGS(int a, int b, int P) { if (1 % P == b) return 0; int d = gcd(a, P); if (d == 1) return BSGS(a, b, P); if (b % d) return -2e9; int ans = exBSGS(a, 1LL * b / d * inv(a / d, P / d) % (P / d), P / d); return ans + (ans != -1); }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/18020851
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)