二次剩余
引入
二次剩余可以认为是在讨论求模意义下 开平方 运算的可行性。对于更高次方的开方可参见 k 次剩余。
定义
二次剩余
令整数
,
满足
,若存在整数
使得
则称
为模
的二次剩余,否则称
为模
的二次非剩余。后文可能在模
显然的情况下简写成二次(非)剩余。
Euler 判别法
当模数为奇素数时,我们有如下定理:
Euler 判别法
对奇素数
和满足
的整数
,有
即对上述的
和
,
是
的二次剩余当且仅当
.
是
的二次非剩余当且仅当
.
证明
首先由 Fermat 小定理 有
,故
从而对任意满足
的
均有 
另外由
是奇素数,我们有:
其中
是某个整系数多项式,进而:
由 同余方程的定理 5 可知,
是模
的二次剩余当且仅当
. 进而
是模
的非二次剩余当且仅当
.
基于 Euler 判别法,我们可以得到如下推论:
二次剩余的数量
对于奇素数
,模
意义下二次剩余和二次非剩余均有
个。
证明
根据 Euler 判别法,考虑 
注意到
,由 同余方程的定理 6 可知
有
个解。所以模
意义下二次剩余和二次非剩余均有
个。
Legendre 符号
为了方便接下来的讨论,我们引入如下记号:
Legendre 符号
对 奇素数
和整数
,定义 Legendre 符号如下:
即对于
的
,
是模
的二次剩余当且仅当 
是模
的二次非剩余当且仅当 
下表为部分 Legendre 符号的值(From Wikipedia)

性质
-
对任意整数
,
进一步,我们有推论:
-
-
-

-
(完全积性)对任意整数
,
我们有推论:对整数
,
有
-
证明
- 由 Legendre 符号的定义 和 Euler 判别法 易得。
-
注意到
而
且
,故:
-
由 1 得
而
且
,故
-
参见 二次互反律
基于如上性质,若对任意奇素数
和
,
的值均可计算,则我们就可以对任意合法情况计算 Legendre 符号的值。接下来我们有一个优美的定理,这个定理巧妙地在
和
之间建立起了联系,使得我们能用类似 辗转相除法 的思路完成计算。
二次互反律
二次互反律
设
,
是两个不同的奇素数,则
证明方式很多。一种证明方式是基于如下引理:
Gauss 引理
设
是奇素数,
,对整数
,令
为
模
的最小非负剩余,设
,
,则
证明
设
,
,显然
,则
我们知道对
中任意元素
,有
,所以
,进一步,对
中任意元素
,我们有
,否则若
中分别存在元素
使得
,则存在整数
使得
,
且
,由于
,则
,注意到
,所以产生矛盾。因此
即
从而由 Legendre 符号的 性质 1 即得证。
容易得到如下推论:
推论
对奇素数
,有
对奇素数
,奇数
满足
,有
证明
对 Gauss 引理中的
,有
,进而
因此
若
,则
,从而有
若
,则有
根据如上推论,证明二次互反律只需验证
考虑由点
,
构成的集合
,将其根据
与
的大小关系分成两部分(显然
),分别验证三个集合的大小即可。
二次互反律不仅能用于判断数
是否是模
的二次剩余,还能用于确定使数
为二次剩余的模数的结构。
Example
- 使得
为二次剩余的奇素数
满足 
- 使得
为二次剩余的奇素数
满足 
- 使得
,
同时为二次剩余的奇素数
满足 
另外,我们还可以证明诸如「形如
的素数有无限多个」之类的结论,这一类结论实际上是 Dirichlet 定理 的简单推论。
Jacobi 符号
根据二次互反律,我们可以很自然地想到一种推广 Legendre 符号的方法:
Jacobi 符号
对 正奇数
和整数
,其中
是素数,
是正整数,定义 Jacobi 符号如下:
其中等式右端的
为 Legendre 符号。另外对整数
有 
Warning
我们一般不区分 Legendre 符号和 Jacobi 符号,因为由完全积性可知 Jacobi 符号具有和 Legendre 符号一样的性质,所以这两种符号的计算方法是一致的。但是有一点需要注意:当
不是奇素数 时,
的值与
是否是模
的二次剩余 无关,但是若
,则说明
至少存在一个(实际上是奇数个)素因子
使得
是模
的二次非剩余,从而此时
是模
的二次非剩余。
我们还可以把模数进一步推广为 整数(只需补充
、
和
的定义),这样就得到了 Kronecker 符号。
相关算法
特殊情况时的算法
对于同余方程
,其中
为奇素数且
为二次剩余在
时有更简单的解法,考虑
那么
为一个解。
Atkin 算法
仍然考虑上述同余方程,此时
,那么令
和
那么此时
且
为一个解。
证明
那么
Cipolla 算法
Cipolla 算法用于求解同余方程
,其中
为奇素数且
为二次剩余。
本节考虑
中的运算,其中
。
计算方法
不熟悉 多项式环 的读者,可以简单理解为该集合的元素都具有形式
且
,且遵循如下运算法则:
需要注意的是,此处的
并不是一个具体的数,而是表示多项式中的形式记号。运算中一个关键的点在于利用
将二次项转化为一次项和常数项。另外,所有整数运算都需要对
取模。
关于该结构的更多内容,请参见 多项式 和 域论 等页面。
该算法的第一步为找到一个
使得
为二次非剩余。当然对于
不可能找到这样的
,需要进行特判。下文只讨论
的情况。此时可随机一个
然后判断,期望可以
步找到。于是,
为一个解,可以通过快速幂求值。
为什么期望只需要两步
考虑
为二次剩余的情况,则存在一个
使得
,移项可得
,不难发现每一个
,都一一对应于一组
的解,所以使原方程成立的解一共有
组。我们分类讨论
和
两种情况。对于
,由于
是二次剩余,对应了
种
的取值;对于
,有
种情况,每一个
对应其中两种,一共有
种
的取值。综上,一共有
种情况使得
为二次剩余,所以每随机一次得到二次非剩余的概率就是
,期望步数为
。
证明
为了方便,首先令
。
需要证明的是,
是原式的解,并且它属于
。首先考虑证明前者,即证明
。为此,我们需要先证明两个引理:
引理 1: 
证明:
引理 2:
使用二项式定理容易发现,除了第一项和最后一项,分子上的
无法消掉,于是只剩下
。
有了这两个引理,我们再来考虑证明原式:
下面通过反证法证明我们求出的解属于
,即其
的系数为
。
假设存在一个
满足
,即
,移项并化简可得:
式子左边的
的系数为
,所以右边
的系数也为
,即
,由于我们令
,所以一定有
,于是
即
。
由于
和
都是二次剩余,由 Legendre 符号的积性可知
也是二次剩余,这与
是二次非剩余矛盾。于是原式不存在一个解使得
的系数非
,我们求出的解的
的系数也必定为
。
模板题 洛谷 P5491【模板】二次剩余
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 | #include <iostream>
#include <random>
long long p, v; // 分别是模数和 r^2 - a 的值
struct Poly {
long long a, b;
Poly(long long _a = 0, long long _b = 0) : a(_a), b(_b) {}
};
Poly operator*(const Poly& x, const Poly& y) {
// 重载乘法,可以参考上面有关运算性质的说明
return Poly((x.a * y.a + v * (x.b * y.b % p)) % p,
(x.a * y.b + x.b * y.a) % p);
}
// 多项式的快速幂,用于计算答案
Poly modpow(Poly a, long long b) {
Poly res(1, 0);
while (b) {
if (b & 1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
// 普通的快速幂,用于判断二次非剩余
long long modpow(long long a, long long b) {
long long res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
// 用于生成随机数
std::mt19937 rng(std::random_device{}());
long long cipolla(long long a, long long _p) {
p = _p;
if (a == 0)
return 0; // 特判一下 0 的情况
else if (modpow(a, (p - 1) / 2) == p - 1)
return -1; // 判断二次非剩余,此时无解
else {
// 随机 r,使得 r^2 - a 是一个二次非剩余
long long r;
for (r = rng() % p;; r = rng() % p) {
if (modpow((r * r - a + p) % p, (p - 1) / 2) == p - 1) break;
}
v = (r * r - a + p) % p;
return modpow(Poly(r, 1), (p + 1) / 2).a; // 根据结论式计算结果
}
}
int main() {
int t, a, p;
std::cin >> t;
while (t--) {
std::cin >> a >> p;
int ans = cipolla(a, p);
if (ans == -1)
std::cout << "Hola!" << std::endl;
else if (ans == 0)
std::cout << 0 << std::endl;
else {
// 相反数是另一个解
int ans2 = (p - ans) % p;
if (ans2 < ans) std::swap(ans, ans2);
std::cout << ans << " " << ans2 << std::endl;
}
}
return 0;
}
|
Bostan–Mori 算法
该算法基于 Cipolla 算法,我们将问题转换为 常系数齐次线性递推 再应用 Bostan–Mori 算法。考虑另一种常见的 Cipolla 算法的描述为
为满足
的一个解,其中
为不可约多项式。选取
同样使用随机。证明过程略。参考文献中的算法我们可以发现问题可转化为求解形式幂级数的乘法逆元的某一项系数:
且
而
时显然有
,该算法乘法次数相较于 Cipolla 算法更少,其他相关乘法次数较少的算法可见。
Legendre 算法
对于同余方程
,其中
为奇素数且
为二次剩余。Legendre 算法可描述为找到
满足
为二次非剩余,令
,那么
且
.
证明
考虑选择一个
满足
,那么
为二次非剩余,所以
存在环态射
那么
所以
而
.
Tonelli–Shanks 算法
Tonelli–Shanks 算法是基于离散对数求解同余方程
的算法,其中
为奇素数且
为模
的二次剩余。
令
其中
为奇数。仍然使用随机方法寻找
满足
为二次非剩余。令
且
,那么存在整数
满足
。若
为二次剩余,那么
为偶数且
.
证明
而
所以
的阶为
,又因为
是
的解,所以
是
的幂次,记
.
若
是二次剩余,那么
所以
为偶数,而
剩下的问题是如何计算
,Tonelli 和 Shanks 提出一次确定
的一个比特。令
在二进制下表示为
其中
.
因为
是二次剩余,所以开始时
,然后计算
然后
等等,由以下公式给出
正确性显然。
习题
参考资料与注释
- Quadratic residue - Wikipedia
- Euler's criterion - Wikipedia
本页面最近更新:2025/7/28 23:58:20,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:Tiphereth-A, ShaoChenHeng, StudyingFather, hly1204, Great-designer, TachikakaMin, Xeonacid, c-forrest, Enter-tainer, sshwy, Chrogeek, iamtwz, marscheng1, monkeysui, nanmenyangde, xyf007, rgw2010
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用