同余代数
数论中的同余代数和数论函数是数论在 OI 中的主要应用,本篇笔记总结了同余代数相关知识点。
尽管近年来的重大考试当中少有涉及同余代数的题目,但熟练掌握相关的各种算法和它们的推导过程对我们的思维水平有很大的锻炼。
数论和抽象代数的联系是非常密切的,以至于同余代数几乎完全属于抽象代数的范畴。如果能够从抽象代数(尤其是循环群)的角度自顶向下地学习同余代数(在 OI 中),那么它将非常简单且容易理解。例如:
- 循环群在同余代数中的最直接应用就是模
下的加法(见 1.10 小节)。 - 中国剩余定理的另一种形式
(见 3.3 小节)。 - 考察
的结构性质给出了原根的存在性定理和在模意义下开 N 次根的算法(见 6.3 小节)。
打星号的小节(不含例题)需要一些抽象代数的前置知识,见 OI 的群论部分或抽象代数课程笔记。链接附在了参考资料部分。
定义与记号
Abstractness is the price of generality.
为了更好地理解同余理论相关算法,读者需熟知基本概念,如素数的定义,算数基本定理,同余符号及其含义,最大公约数等,并掌握基本算法如快速幂,辗转相除法求最大公约数(Euclid 算法)等。
说明
在模
定义
- 整除:若非零整数
是整数 的因数,则称 整除 或 被 整除,记作 。如 , 。 - 同余:若整数
模正整数 的余数相等,则称 在模 下同余,记作 。如 , 。 - 最大公约数:整数
和 的 最大公约数 为最大的整数 满足 整除 和 ,记作 。如 , 。 - 互质:若整数
满足 ,则称 互质,记作 。一般 均为非负整数。如 , , 。 - 剩余类:模
同余的所有数构成的等价类被称为模 的 剩余类。模 下它们等价。 - 完全剩余系:
构成模 的 完全剩余系,记为 。 - 简化剩余系:所有小于
且与 互质的正整数构成模 的 简化剩余系,又称 既约剩余系 或 缩系,记为 。这个集合的大小为 。
注意区分因数和因子:因数表示一个数的约数,因子表示乘积的一项。
记号
- 素数集:记
表示素数集。 。 - 质因数次数:
所含质因数 的次数记作 。 且 。 - 各位数字和:
在 进制下的各位数字之和记作 。
基本知识(非常重要)
- 给定
,使得 的最小的 为 ,且所有这样的 恰为 的倍数。对每个质因数单独分析即得。特别地,若 且 ,则 。 - 对
,在 中使得 的 的个数等于在 中使得 的 的个数,即 。因为 等价于 。
1. 基础知识
OI 中的同余理论主要是 在模意义下解方程。
1.1 同余的性质
了解同余的性质是学习同余数论的基础。同余符号的根本含义:
在同余式中,加法和乘法的交换律与结合律以及乘法分配律依然成立。以下两条性质是进行模意义下计算的依据。
- 两整数相加(减)再对
取模等于两个数先对 取模再相加(减),再对 取模。 - 两整数相乘再对
取模等于两个数先对 取模再相乘,再对 取模。
证明
设
, ,其中所有数都是整数且 ,即 , 。因为 ,所以 乘法同理。
和等式一样,同余式的基本性质:若
需要注意,即使
1.2 Fermat 小定理
要求模数
引理
当
是质数时,其因数只有 和 。因此,若两个数相乘是 的倍数,则其中至少有一个是 的倍数。 这个引理很重要。
当
考虑
因为
该结论称为 Fermat 小定理。根据推导过程,它适用于
1.3 乘法逆元
定义
为了支持同余式的除法运算,需要引入乘法逆元的概念:对整数
乘法逆元不一定存在,也不一定唯一。在实际运算中,只需要任意一个。
考虑同余式
求法
根据 Fermat 小定理,当
据此快速幂求出一个数在模质数下的乘法逆元。
当
以下给出几个模 质数
前缀逆元
增量法,设
设
时间复杂度线性。模板题。
离线
求任意
设
时间复杂度
在线
首先
不能对每个
最简单的分组方式是整除。设阈值
目标:对每个
枚举每个
对每个
复杂度
1.4 Lagrange 定理
我们知道代数学基本定理:
Lagrange 定理:对于
证明
考虑数学归纳法。当
时定理成立,因为 无解(注意 )。 当
时,假设方程有 个不同解 。因为 ,所以 ,其中 是不超过 次的多项式。 因为
且 ,故 均为 的根,矛盾。
解的数量并非恰为
- 注意:
不是质数时结论不成立,如 有解 。
1.5 Wilson 定理
一般形式
乘法逆元是相互的。若
对
考虑
- 当
时,考虑 和 。它们相乘后模 等于 。因此,若 ,即 为大于 的完全平方数时, ; - 当
时, ; - 当
为大于 的非完全平方数时,令 为 的最小质因数,则 ,所以 .
综上,我们得到 Wilson 定理:
扩展形式
尝试将 Wilson 定理扩展至素数幂的情形。
考虑
仍然考虑求解
因式分解得
当
当
注意
综上,得到 Wilson 定理的扩展形式
在 exLucas 中用到了该结论。
1.6 Kummer 定理
阶乘的素数幂次
给定正整数
提出
该结论由 Legendre 在 1808 年提出。
考虑换一种方法求和。尝试对
其中用到了等比数列求和公式
通过上述推导,得到
组合数的素数幂次
根据组合数公式
考虑
该结论称为 Kummer 定理。
1.7 步长与子环
结论
在长为
的环上每一步走 条边,形成 个子环,每个环的环长为 。
按顺序给环上的每个点标号
引理
当
时,从环上任意一点出发,能够走遍所有点。 证明
即证
对 互不相同。假设存在 有 ,移项得 。因为 ,所以 ,这与 矛盾。 说明
这个引理本质上和最开始的基本知识等价。如果跳
步回到原来的位置,那么 ,推出 是 的倍数。但 ,所以 无论如何都是 的倍数。
即当
不妨设
因为
于是子环长为
1.8 Euler 定理
前置知识:Euler 函数(见数论 II)。
一般形式
回顾 Fermat 小定理的证明过程,我们发现
设
等式两边同时除以
在计算与模数互质的某个数的幂时,指数可以对模数的 Euler 函数取模。可以用来化简公式或减小常数,前提是模数是定值或其 Euler 函数容易计算。
当
扩展形式
当
证明
设
。在模 下考虑 ,它们均为 的倍数。因为 ,所以它们除以 之后会取遍所有与 互质的数。根据 Euler 定理, 有循环节 ,因此 即 也有长度为 的循环节。而 ,所以从 开始,对 , 有长度为 的循环节。 还需证明
。感性理解就是如果 gcd 变化,一定会乘上一个因子,所以 是 级别的,会比 小。具体怎么证明呢?我们发现 就是 的每个质因数幂次 对应的 的最大值,所以只需对 是质数幂次 的情况证明。设 含 个质因数 ,则 。因此,显然地,我们只需对 是质数 的情况证明,因为此时 最大。 设
,则 。由 Euler 函数的性质, 。
1.9 同余式的除法
对于
读者需要区分这两种对同余式的基本变形方法。同余式的本质为存在
*1.10 循环群和直积
循环群 是只由一个元素生成的群
对
特别地,当同时需要模
定义两个群
2. 二元线性不定方程
扩展 Euclid 算法(exgcd)用于求解形如
2.1 Bezout 定理
在求解
首先考虑一些较弱的结论(必要条件)。无论
根据直观感受,如果
因此,对任意整数
综上,我们得到了 Bezout 定理:二元整数线性不定方程
2.2 扩展 Euclid 算法(exgcd)
欲求解
注意到等式右端等于
因为
对比等式两侧,令
扩展 Euclid 算法的时间复杂度是辗转相除法的
void exgcd(int a, int b, int &x, int &y) {
if(!b) return x = 1, y = 0, void();
exgcd(b, a % b, x, y);
int _y = x - (a / b) * y;
x = y, y = _y;
}
也可以写成
void exgcd(int a, int b, int &x, int &y) {
if(!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
- 注意:exgcd 求得的解为
的一组特解。为得到原方程 的特解,还需将 乘以 。
2.3 扩展
通解形式
若
使用扩欧求得特解
将
特解的数值范围
关于 exgcd 求得特解的数值范围,详见 博客。结论如下:对于非平凡情况(
应用
-
解一元线性同余方程
。看成二元线性不定方程 使用 exgcd 求解。根据 Bezout 定理,方程有解当且仅当 。 -
当模数不是质数时,Fermat 小定理不再适用,但模意义下的乘法逆元仍可能存在。
在模 下存在逆元当且仅当 ,因为求逆元相当于解 。 -
在 时无解,否则在 中有 个整数解。这是因为其通解可写为 的形式,其中 :将方程两侧同时除以 ,则 。
2.4 例题
P5656【模板】二元一次不定方程 (exgcd)
题目要求
#include <bits/stdc++.h>
using namespace std;
#define int long long
int T, a, b, c;
void exgcd(int a, int b, int &x, int &y) {
if(!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
signed main() {
cin >> T;
while(T--) {
scanf("%lld %lld %lld", &a, &b, &c);
int d = __gcd(a, b), x, y, xx, xy, yx, yy;
if(c % d) {
puts("-1"); continue;
}
exgcd(a, b, x, y);
x *= c / d, y *= c / d;
xx = x % (b / d);
if(xx <= 0) xx += b / d;
xy = (c - a * xx) / b;
yy = y % (a / d);
if(yy <= 0) yy += a / d;
yx = (c - b * yy) / a;
if(xy <= 0) cout << xx << " " << yy << "\n";
else cout << (yx - xx) / (b / d) + 1 << " " << xx << " " << yy << " " << yx << " " << xy << "\n";
}
return 0;
}
UVA12775 Gift Dilemma
记
CF1728E Red-Black Pepper
先通过一遍贪心对每个
对每组询问,exgcd 求出使得
时间复杂度
[模拟赛] 你还没有 AK 吗
给定
,求整数 , ,执行任意正整数次 和 后使得 的初始值 的个数。 多组数据。
, 。
令
Fibonacci 数列增长很快,考虑直接枚举
不难发现只有本质不同的
进一步地,有 __int128
。
*P3518 [POI2011] SEJ-Strongbox
若
证明
设
, 且 不是密码,则 无解。根据 Bezout 定理,它等价于 即 ,矛盾。
进一步地,若
因此,设密码集合为
考虑枚举这个
设
首先给所有
剩下没有被打标记的
打标记的过程可以使用哈希表实现,时间复杂度
*P3421 [POI2005] SKO-Knights
题目要求我们找到两个向量
记
Sol 1
如果我们能将三个向量
- 交换:对应本题即任意交换
。 - 倍乘:对应本题即乘以任意非
整数。 - 倍加:对应本题即向量加上任意整数倍其它向量。
当然,上述性质仅在 实系数 线性组合的前提下成立。当要求系数为 整数 时:
- 交换显然不改变张成。
- 倍乘变换 可能改变 张成,因为整数乘法不可逆。如
显然不等于 。 - 倍加变换 不改变 张成。对任意
,它仍是 的整系数线性组合 。对任意 ,我们也总可以将其表示为 和 的整系数线性组合 。
很好!如果整系数倍加变换不改变整系数张成,就可以使用 辗转相减法。
具体地,考虑两个向量
考虑三个向量
综上,我们在
注意题目限制坐标绝对值不超过
从上述过程中,我们发现一个有趣的性质:整系数下,两个向量可以在不改变其张成的前提下,使其中一个向量的某一维变成
Sol 2
让我们深入地钻研一下题解区的其它做法。
实际上两者的核心思想是等价的:将两个向量的其中一个的某一维变成
不妨设对于向量
对于
综上,我们将
Sol 3
另一种求解
考虑
若
抛砖引玉:联立后两种方法描述
CF516E Drazil and His Happy Friends
不妨设
如果一个男生被一个女生变得快乐,那么要么这个女生一开始就是快乐的,要么这个女生先被别的男生变得快乐。对于前者,总共只有
男生的
当
时间复杂度
*P3543 [POI2012] WYR-Leveling Ground
区间操作转化为差分数组
首先考虑对每个差分值单独求解,解不定方程
先不考虑可行性。因为
当前
根据特解的形式
当
容易证明调整一个数的代价随着调整次数增加仅在
一个神奇的性质是
忽略问题限制得到基本解法再调整,巧妙结合反悔贪心和 exgcd。代码。
*P7325 [WC2021] 斐波那契
解决本题需要的知识:对任意整数
首先特判
自然,我们希望将
- 化简的核心思路是,同余方程
等价于 ,其中 均为代数式, 。直观理解很显然,感性说明就是 在该同余方程中完全没有起到任何作用。
令
其中
欲将等式两侧同时除以
令
其中
欲将等式两侧同时除以
以上推导过程说明我们需要枚举 map
。根据一开始的结论,复杂度是 map
的
注意到
注意:当
总结一下,我们在化简过程中,假设了
3. 线性同余方程组
前置知识:扩展 Euclid 算法。
形如
的方程组称为 线性同余方程组,其应用非常广泛(从三模 NTT 到高次剩余)。最常见的用处是将模任意合数简化为模指数幂。特别地,当模数不含平方因数时(square-free number,
exCRT 比 CRT 更简单且适用范围更广泛,因此笔者在合并线性同余方程组时一般使用 exCRT。
3.1 中国剩余定理(CRT)
中国剩余定理(Chinese Remainder Theorem,CRT)用于求解 模数两两互质 的线性同余方程组。对任意
CRT 的思想是求出若干个数,使得这些数依次满足每个同余方程,且在其它模数下均为
我们尝试为每个同余方程构造
为满足条件二,
为满足条件一,需要给
对每个模数进行上述流程,则 __int128
。
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 10 + 5;
int n, a[N], b[N];
long long M = 1, ans;
void exgcd(int a, int b, int &x, int &y) {
if(!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
int inv(int x, int p) {
return exgcd(x, p, x, *new int), (x % p + p) % p;
}
int main() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i] >> b[i], M *= a[i];
for(int i = 1; i <= n; i++) ans = (ans + (__int128)b[i] * (M / a[i]) * inv(M / a[i] % a[i], a[i])) % M;
cout << ans << endl;
return 0;
}
3.2 扩展中国剩余定理(exCRT)
扩展中国剩余定理(exCRT)用于求解一般形式的线性同余方程组。除了求解的问题相似,它和 CRT 并没有太大联系。
当模数不互质时,考虑合并两个同余方程
根据 Bezout 定理,若
3#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll n, a, b, c, d;
void exgcd(ll a, ll b, ll &x, ll &y) {
if(!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
int main() {
cin >> n >> a >> b;
for(int i = 1; i < n; i++) {
cin >> c >> d;
ll e = __gcd(a, c), f = (d - b % c + c) % c, inv;
c /= e, f /= e;
exgcd(a / e, c, inv, *new ll), inv = (inv % c + c) % c;
b += (__int128)f * inv % c * a, a *= c, b %= a;
}
cout << b << endl;
return 0;
}
注意考虑 exgcd 解出来是负数的情况。在得到解之后要进行取模。
*3.3 抽象代数中的 CRT
当且仅当
实际上,一个双射由
需要指出的是,抽象代数中的 CRT 并没有给出根据
3.4 例题
P4774 [NOI2018] 屠龙勇士
设第
先不管第二个条件,设
为满足第二个条件,先求出
时间复杂度
P4621 [COCI2012-2013#6] BAKTERIJE
细菌的运动情况会在不超过
时间复杂度
P2480 [SDOI2010] 古代猪文
通过组合数学可知题目要求
时间复杂度
4. 组合数取模
前置知识:组合数,二项式定理(组合数学)。
4.1 Lucas 定理
Lucas 定理是连接组合数学和数论的桥梁。
根据 1.6 小节的结论,当
当
引理
当
证明
使用二项式定理将上式拆开,当
时, 是 的倍数。 推论
Lucas 定理
当
证明
设
,则 由二项式定理,上式
前的系数即 。 因为
展开后 的次数均为 的倍数, 展开后 的次数小于 ,因此 项只能由 展开后的 项与 展开后的 项相乘得到。 另一种形式
设
在 进制下为 和 ,则 显然,两种形式是等价的。
根据 Lucas 定理,计算大组合数对
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
int T, n, m, p, fc[N], ifc[N];
int ksm(int a, int b) {
int s = 1;
while(b) {
if(b & 1) s = 1ll * s * a % p;
a = 1ll * a * a % p, b >>= 1;
}
return s;
}
int binom(int n, int m) {
return 1ll * fc[n] * ifc[m] % p * ifc[n - m] % p;
}
int lucas(int n, int m) {
if(n % p < m % p) return 0;
if(n < p) return binom(n, m);
return 1ll * lucas(n / p, m / p) * binom(n % p, m % p) % p;
}
void solve() {
cin >> n >> m >> p;
for(int i = fc[0] = 1; i < p; i++) fc[i] = 1ll * fc[i - 1] * i % p;
ifc[p - 1] = ksm(fc[p - 1], p - 2);
for(int i = p - 2; ~i; i--) ifc[i] = 1ll * ifc[i + 1] * (i + 1) % p;
cout << lucas(n + m, n) << endl;
}
int main() {
cin >> T;
while(T--) solve();
return 0;
}
在
Lucas 定理还可以快速计算组合数奇偶性。
4.2 扩展 Lucas 定理(exLucas)
当模数
模合数的问题的第一步一定是 CRT,将问题简化为模指数幂的情况。具体地,设
考虑
使用 Kummer 定理计算组合数的质因数幂次(
设
对于
对于不是
综上,
当
- 注意:递归的子问题是除以
,而循环节数量是除以 。 - 注意:区分
和 。前者只是将所有 去掉,而后者将含有质因数 的所有数去掉了。
综上,我们在
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void exgcd(int a, int b, int &x, int &y) {
if(!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
int inv(int x, int p) {
return exgcd(x, p, x, *new int), (x % p + p) % p;
}
ll n, m;
int p, tp, ans, curp = 1, q, qk, fc[1000005];
ll num(ll n) {
ll s = 0;
while(n) s += n /= q;
return s;
}
int calc(ll n) {
if(!n) return 1;
return 1ll * calc(n / q) * (tp && (n / qk & 1) ? qk - 1 : 1) % qk * fc[n % qk] % qk;
} // 注意 n / q 和 n / qk!
int solve() {
tp = (qk & 1) || qk <= 4; // 扩展 Wilson 定理.
for(int i = fc[0] = 1; i < qk; i++) fc[i] = 1ll * fc[i - 1] * (i % q ? i : 1) % qk;
int pw = num(n) - num(m) - num(n - m);
int res = 1ll * calc(n) * inv(1ll * calc(m) * calc(n - m) % qk, qk) % qk;
while(pw--) res = 1ll * res * q % qk; // 根据 Kummer 定理, 质因子在组合数中的个数为对数级别.
return res;
}
int main() {
cin >> n >> m >> p;
for(int i = 2; i <= p; i++) {
if(i * i > p) i = p;
if(p % i == 0) {
q = i, qk = 1;
while(p % i == 0) qk *= i, p /= i;
ans += 1ll * (solve() - ans % qk + qk) * inv(curp, qk) % qk * curp;
ans %= (curp *= qk); // exCRT 合并更方便.
}
}
cout << ans << endl;
return 0;
}
4.3 例题
[BZOJ2445] 最大团
定义一张
个点的图是 “好图” 当且仅当它能被分成若干大小相等的完全图。结点有标号。记 个点的好图个数为 。求 。 。
模数是质数,Fermat 小定理转化为求
被分成的完全图大小
根据组合意义,从
观察
根据 1.6 小节的结论,先求出答案含有多少个质因子
*P5598 【XR-4】混乱度
设
固定
预处理每个非零的
时间复杂度
5. 离散对数问题
离散对数问题即求解 离散对数方程
其中
5.1 大步小步算法(BSGS)
大步小步算法(Baby Step Giant Step,BSGS)要求
BSGS 使用了 meet-in-the-middle:设块长为
根据 Euler 定理,若有解,则在
#include <bits/stdc++.h>
using namespace std;
int BSGS(int a, int b, int p) {
int A = 1, B = sqrt(p) + 1;
a %= p, b %= p;
unordered_map<int, int> mp;
for(int i = 1; i <= B; i++) {
A = 1ll * A * a % p;
mp[1ll * A * b % p] = i;
}
for(int i = 1, AA = A; i <= B; i++) {
if(mp.find(AA) != mp.end()) {
return i * B - mp[AA];
}
AA = 1ll * AA * A % p;
}
return -1;
}
int main() {
int p, b, n;
cin >> p >> b >> n;
int ans = BSGS(b,n,p);
if(~ans) cout << ans << "\n";
else puts("no solution");
return 0;
}
根据枚举顺序可知以上代码求得
5.2 扩展大步小步算法(exBSGS)
对于更一般的离散对数方程,没有
从已知到未知。既然可以解决
注意到
因为
显然
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
int a, p, b;
void exgcd(int a, int b, int &x, int &y) {
if(!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
int inv(int x, int p) {return exgcd(x, p, x, *new int), (x % p + p) % p;}
int BSGS(int a, int b, int p) {
a %= p, b %= p;
int A = 1, B = sqrt(p) + 1;
gp_hash_table <int, int> mp;
for(int i = 1; i <= B; i++) mp[1ll * (A = 1ll * A * a % p) * b % p] = i;
for(int i = 1, AA = A; i <= B; i++, AA = 1ll * AA * A % p) if(mp.find(AA) != mp.end()) return i * B - mp[AA];
return -1;
}
int exBSGS(int a, int b, int p) {
int d = __gcd(a, p), cnt = 0;
a %= p, b %= p;
while(d > 1) {
if(b == 1) return cnt;
if(b % d) return -1;
p /= d, b = 1ll * b / d * inv(a / d, p) % p;
d = __gcd(p, a %= p), cnt++;
}
int ans = BSGS(a, b, p);
return ~ans ? ans + cnt : -1;
}
int main() {
cin >> a >> p >> b;
while(a) {
int ans = exBSGS(a, b, p);
if(~ans) cout << ans << "\n";
else puts("No Solution");
cin >> a >> p >> b;
}
return 0;
}
5.3 例题
P2485 [SDOI2011] 计算器
操作 1 快速幂,操作 2 exgcd 求逆元,操作 3 BSGS。
P3306 [SDOI2013] 随机数生成器
把
对同余式进行简单变形后得到离散对数问题,使用 BSGS 求解。时间复杂度
6. 阶与原根
对于含乘法的同余式
在模数是合数时,一般会将问题化为只考虑和模数互质的数。
对
6.1 阶及其性质
定义
必要性
若
与 不互质,则 与 不互质,模 不可能得 。 充分性
若
,根据 Euler 定理, 即一解,因此阶存在。
以下设
性质
阶本质上在刻画
更具体地,
性质 1
当且仅当 。 证明
若
,则 。因此若 ,则 。 若
,设 ,则 。因为 ,所以 。
考虑一个数自身相乘时,它的阶如何变化。如果我们将模
性质 2
证明
注意到
。 对于第一部分,根据性质 1,
为最小的 使得 。于是 (见最开头的基本知识)。 对于第二部分,同时是
和 的倍数的最小正整数为 ,所以使得 的最小的 即 。于是 。 以上两部分也可以根据
相互推导。
考虑两个数相乘时,它们的阶如何变化。和一个数自身的幂次
如果
性质 3
当且仅当 。 证明
必要性是显然的。
充分性的证明思路是注意到对所有
的倍数或 的倍数但不为 的倍数 ,对于 和 ,一定恰有一个等于 ,另一个不等于 ,所以 一定不是 的倍数。 两个最小循环节分别为
和 的序列相乘,一定有 的循环节,最小循环节显然是 的因数。如果最小循环节不等于 ,那么存在 的质因数 使得 。不妨设 ,则 ,其中 。因为 ,所以 不是 的倍数。但 ,矛盾。这说明最小循环节等于 。
对于更一般的情况呢?读者可以先结合以上两种特殊情况自行思考。
性质 4
对于质因数
,如果它在 和 的次数相等,那么它在 的次数是任意的,但不会超过它在 的次数。如果不等,那么它在 的次数是它在 和 的次数的较大值。 可以这样类比:设
在 的次数为 ,在 的次数为 。如果 ,那么 在 的次数 可以任意大。如果 ,那么 。至于为什么是加法,考虑到阶本身是描述指数循环节的,而相乘转化到指数上就是加法。 如果
有原根 ,那么一切都是好理解的: ,指数在分母上解释了一个是任意小一个是任意大,一个是较大值一个是较小值。
性质 4 描述了乘积的阶与每个因子的阶的关系。
性质 2 和性质 4 给予我们操作阶的空间,并引出了以下性质。
性质 5
给定
,存在 使得 。 证明
对每个质因数
,如果 在 的次数相等且不为 ,那么让 。由性质 2,因为 ,所以 。处理后 和 没有次数相同的质因数。根据性质 4 可知 。
离散对数的通解形式:当
求法
法一:使用 BSGS 求
法二:根据性质 1,对
6.2 原根
以下设
定义
若
我们需要原根是因为它可以 生成 模
简单地说,它可以将乘法转化为加法。
性质
判定原根的一般方法是直接计算阶。如果
原根判定定理
是 的原根当且仅当对任意 的质因子 ,均有 。
除了判断一个数是否是原根,我们还需要判断一个数是否有原根。
原根存在定理
有原根当且仅当 ,其中 是 奇质数。 证明较复杂,不感兴趣的读者可以跳过。
有原根是显然的。 奇质数
对奇质数
,由引理 5,存在 使得对任意 , ,所以 有 个不同的解。由 Lagrange 定理, 。 奇质数幂
考虑数学归纳。
设
是奇质数 的原根。如果 ,那么 设
是奇质数幂 的原根, ,且 (当 时,上述说明保证这样的 存在),那么 于是
且
是奇质数幂 的原根。由数学归纳法可知 是任意 的原根。
- 请思考以上推导过程在哪里对
不适用。 设
是 的原根,考虑 可知 。所以 是 的原根。 必要性
首先,如果
有原根,那么 的所有因数都有原根。所以只需证明 , 和 没有原根。 没有原根是显然的。 对任意
,因为 是 的倍数,所以 且 。由 CRT, 。 对任意
,因为 都是 的倍数,所以 且 。由 CRT, 。
如果一个数有原根,那么其缩系上的乘法等价于模
性质 6
若
有原根,则使得 的 的个数为 证明
根据性质 1,第一部分显然。考虑第二部分。
设
是 的原根。因为任意 均可表示为 ,所以答案即使得 的 的个数。根据性质 2, ,因此 ,即 。由基本知识, 的个数为 。 形象地,将
看成长度为 的环。在环上每次走 步形成子环,则 的阶等于子环环长。由 1.7 小节,环长为 。为使 ,则 。
性质 6 反映了欧拉函数的性质
原根个数定理
若正整数
有原根,则原根个数为 。
求法
求一个数的所有原根:求出任意一个原根
中国数学家王元证明了质数的最小原根的数量级为
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e6 + 5;
int ksm(int a, int b, int p) {
int s = 1;
while(b) {
if(b & 1) s = 1ll * s * a % p;
a = 1ll * a * a % p, b >>= 1;
}
return s;
}
void solve() {
int n, d;
cin >> n >> d;
int x = n, phi = n;
for(int i = 2; i <= x; i++) {
if(x % i == 0) {
phi = phi / i * (i - 1);
while(x % i == 0) x /= i;
}
}
vector<int> pr;
x = phi;
for(int i = 2; i <= x; i++) {
if(x % i == 0) {
pr.push_back(i);
while(x % i == 0) x /= i;
}
}
for(int i = 1; i < n; i++) {
if(__gcd(i, n) != 1) continue;
bool ok = 1;
for(int it : pr) {
ok &= ksm(i, phi / it, n) != 1;
}
if(ok) {
static int is[N], g[N];
memset(is, 0, N << 2);
is[i] = 1;
for(int j = 2; j <= phi; j++) {
if(__gcd(j, phi) == 1) {
is[ksm(i, j, n)] = 1;
}
}
int cnt = 0;
for(int j = 1; j <= n; j++) {
if(is[j]) g[++cnt] = j;
}
cout << cnt << "\n";
for(int p = 1; p <= cnt / d; p++) {
cout << g[p * d] << " ";
}
cout << "\n";
return;
}
}
cout << "0\n\n";
}
int main() {
int T;
cin >> T;
while(T--) solve();
return 0;
}
补充:因为
原根和单位根
若
对于任何
图中,单位根的
因此,对于特定模数,我们可以使用原根代替
类比原根和单位根,得到以下性质。
性质 7
设
是奇质数或奇质数的幂, 是 的原根,则 。 证明
设
,则 。如果 ,那么 ,和 矛盾。因此 。
性质 7 在二次剩余部分有用。
*6.3 的结构
关于记号,见 1.10 小节。
根据 CRT,我们只需要考虑
根据原根的定义,
考虑
事实上,
引理 1
对
, 恰含有 个质因数 。 证明
当
时命题成立。 ,前一项恰因子含有 个质因数 ,是 的倍数,所以后一项因子不是 的倍数。因此 恰含有 个质因数 。 引理 2
对
, 。 证明
因为阶是
的因数但不能等于 (没有原根),所以 。如果 ,那么 含有至少 个质因数 ,和引理 1 矛盾。因此 。
现在我们知道
引理 3
对
和 , 。 证明
如果成立,那么
,但 ,矛盾。
上述推导说明
性质 8
对任意
和 ,存在 和 使得 ,且这种表示唯一,即每个 的奇数唯一对应一组 。
性质 8 是求模
现在让我们站在更高的视角看看为什么
由 3.3 小节,如果
6.4 例题
P5605 小 A 与两位神仙
前置知识:Pollard-rho(数论 III)。
模数
尝试将
直接求阶即可,需要使用 Pollard-rho 分解
本题告诉我们的结论:对于奇质数幂
*P6730 [WC2020] 猜数游戏
和上一题差不多的思路。
分成
当且仅当
当
当
7. N 次剩余问题
N 次剩余问题即求解形如
7.1 定义与符号
对
特别地,对
Legendre 符号:记作
7.2 二次剩余的分布
奇质数
当
也就是说,原根的偶数次幂和奇数次将
进一步地,如果
类比:正数(二次剩余)的平方根有两个且互为相反数,负数(二次非剩余)没有平方根。
奇质数幂
将奇质数的结论稍微推广即可,读者可以先自行尝试推导结论。
设非零整数
以上推导可以推广到 N 次剩余的情况。实际上,这是求解 N 次剩余的一部分。
设
首先考虑
假设
一共有
对于
任意合数
根据 CRT 拆成模质数幂。显然地,对
7.3 二次剩余的判定:Euler 准则
7.2 小节给出了
给定奇质数
对于整数
注意到偶数的
Euler 准则
设
是奇质数。
简单地说,
Euler 准则同时告诉我们 Legendre 符号关于
笔者的理解:由 Fermat 小定理,对奇素数
7.4 模意义下开平方根
考虑求解
7.4.1 模质数下开平方根
设
由 7.2 小节的分析,方程恰有两个解,只需求出任意一个解,通过取相反数得到另一个。
使用 BSGS,显然存在根号时间的算法:先求出
特殊情况的解法:由 Euler 准则,
模意义下扩域
设
该数域满足很多常见的代数性质,如乘法结合律,乘法分配律等,在此不一一证明。其上的运算可以看成模
这个技巧常见于模意义下的二次非剩余强制开方。例如
由 Euler 准则,
Cipolla
我们希望找到一个数的偶数次幂等于
令
也就是说,如果能找到正整数
在 上的正确性:使用二项式定理展开后,考虑到对任意 , 。- 假如存在
,对比两侧虚部得到 。如果 ,那么 ,即 。因为 是二次剩余,所以 是二次剩余(Legendre 符号的完全积性),矛盾。因此 ,即 在 上的二次剩余也一定是在 上的二次剩余( 的二次剩余),也即 的虚部为 。 - 考虑
是二次剩余的 的数量,即存在 使得 即 。固定 得到 ,可以唯一求出一组解 ,而固定 之后对应的 恰有两个(开根有两个解)。去掉 的两种情况( ),可知 的数量为 。因此,使得 是二次非剩余的 的数量为 。这说明期望随机两次 即可得到 。
综上,Cipolla 算法的时间复杂度为
#include <bits/stdc++.h>
using namespace std;
mt19937 rnd(1064);
int T, n, p, b, w;
struct comp {
int a, b;
comp operator + (comp c) {
return {(a + c.a) % p, (b + c.b) % p};
}
comp operator * (comp c) {
return {(1ll * a * c.a + 1ll * b * c.b % p * w) % p, (1ll * a * c.b + 1ll * b * c.a) % p};
}
};
int ksm(int a, int b) {
int s = 1;
while(b) {
if(b & 1) s = 1ll * s * a % p;
a = 1ll * a * a % p, b >>= 1;
}
return s;
}
comp ksm(comp a, int b) {
comp s = {1, 0};
while(b) {
if(b & 1) s = s * a;
a = a * a, b >>= 1;
}
return s;
}
void solve() {
cin >> n >> p;
if(!n) return puts("0"), void();
if(ksm(n, p - 1 >> 1) == p - 1) return puts("Hola!"), void();
do b = rnd() % p; while(ksm(w = (1ll * b * b - n + p) % p, p - 1 >> 1) != p - 1);
int x = ksm({b, 1}, p + 1 >> 1).a;
cout << min(x, p - x) << " " << max(x, p - x) << "\n";
}
int main() {
cin >> T;
while(T--) solve();
return 0;
}
除了 Cipolla 以外,还有能够简单扩展到模质数幂的 Tonelli-Shanks 算法,此处不做介绍,详见参考资料。
7.4.2 任意模数下开平方根
奇质数幂
设模数为
处理模质数幂的第一步一般是先把答案在模质数下求出来。设
因为
把
又因为
求出一个平方根后,取相反数得到另一个,即得所有平方根。
时间复杂度
类似地,我们只考虑
如果
因此,考虑
显然地,该算法可以求出所有平方根。如果认为平方根的数量是常数,则时间复杂度
也可以使用类似开 N 次方根的算法,时间复杂度较劣。见 7.5 小节。
任意模数
使用 CRT 合并即可。如果要求出所有平方根,当
*7.5 任意模数开 N 次根
在掌握了足够多二次剩余相关性质和开平方根的算法之后,我们考虑解决更一般 N 次剩余问题:
奇质数
设
奇质数幂
设模数为
*
类似地,容易简化至
开平方根时的递推算法在这里不适用了,因为递推过程中解的数量可能非常大,但最终它们都无法成为
根据 6.3 小节的结论,所有
两个方程独立,分别求解后再合并即可。
任意模数
使用 CRT 合并即可。如果认为平方根的数量是常数,则时间复杂度
7.6 总结
N 次剩余相关的知识比较难,为了更好地理解这些结论和算法,当模数有原根时,抓住原根等价于单位根的性质,想象一个环是很有帮助的。每个数编号为其以
接下来我们介绍了模意义下开根,这个问题有三个维度:
/ 是剩余。- 模质数 / 奇质数幂 /
的幂 / 任意合数。 / 。
对于不同的情况有复杂度和复杂程度不同的解法。
在模数没有原根的时候,我们以 6.3 小节
限于篇幅和 OI 方面的应用,还有很多内容没有介绍,如二次互反律。感兴趣的读者可以查阅资料自行学习。
7.7 例题
*P6610 [Code+#7] 同余方程
根据
问题转化为求
我们需要数学公式而非判断式来描述答案,因为判断式不可化简。
二次剩余对应
接下来是一步巧妙转化。考虑到给
显然,对于
根据 Euler 准则,上式即
对于
公式足够简洁,可以
*P8457 「SWTR-8」幂塔方程
题解。
CHANGE LOG
- 2021.12.6:重构文章,删去线性筛部分,修改部分表述。
- 2022.3.15:重构文章。
- 2022.3.24:新增 Wilson 定理,素数在阶乘和组合数中的幂次,阶与原根,二次剩余和 Lucas 定理。
- 2022.6.12:勘误,修改表述。
- 2022.6.22:将数论分块移出本文。
- 2024.2.22:增加在线 O(1) 逆元。
- 2025.1.22:重构文章,移除欧拉函数。
- 2025.1.27:补充任意模数开平方根,开 N 次方根和
的结构性质。
参考资料
第一章
- 在线
逆元 —— zhoukangyang。
第二章
- 关于 exgcd 求得特解的数值范围 —— ycx060617。
第六章
- 原根 - OI Wiki。
- The Structure of
—— R. C. Daileda。PDF 链接。
第七章
- Quadratic residue —— Wikipedia。
- Cipolla's algorithm —— Wikipedia。
- 二次剩余 —— Aleph_Drawer(关于
是二次非剩余的 的数量)。 - 题解 P5491 —— cyffff。
- Tonelli-Shanks algorithm —— Wikipedia。
全文:
- 数论基础学习笔记 —— ycx060617。
- 数论 —— OI Wiki。
- 抽象代数课程笔记 I —— 群论 —— Alex_Wei。
- 抽象代数课程笔记 II —— 环论 —— Alex_Wei。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!