8.12.18 ACM-ICPC数学 数论 二次剩余
在数论中,二次剩余是一个非常重要的概念,它在密码学和数论算法中有着广泛的应用。在本节中,我们将详细介绍二次剩余的定义、性质,以及如何使用C++进行实现,并通过例题洛谷 P5491 来进行应用。
二次剩余的定义
对于一个给定的整数𝑛n和一个模𝑝p,如果存在整数𝑥x,使得:
𝑥2≡𝑛 (mod 𝑝)x2≡n (mod p)
那么称𝑛n是模𝑝p的一个二次剩余;否则称𝑛n是模𝑝p的一个二次非剩余。
简单来说,如果存在某个整数𝑥x的平方与𝑛n同余于模𝑝p,那么𝑛n就是模𝑝p的一个二次剩余。
二次剩余的性质
-
存在性:对于一个质数𝑝p,模𝑝p的二次剩余数量为𝑝−122p−1,二次非剩余的数量也为𝑝−122p−1。
-
乘法闭包:如果𝑎a和𝑏b都是模𝑝p的二次剩余,那么𝑎𝑏ab也是模𝑝p的二次剩余。
-
乘法反闭包:如果𝑎a是模𝑝p的二次剩余,而𝑏b是模𝑝p的二次非剩余,那么𝑎𝑏ab是模𝑝p的二次非剩余。
二次剩余的求解算法
求解一个数是否为二次剩余及其对应的平方根有多种方法,其中一种常见的方法是使用Tonelli-Shanks算法。该算法在模数为奇质数时,可以高效地求解二次剩余。
Tonelli-Shanks算法简介
Tonelli-Shanks算法用于求解方程𝑥2≡𝑛 (mod 𝑝)x2≡n (mod p),其中𝑝p是奇质数。算法步骤如下:
- 将𝑝−1p−1分解为𝑞⋅2𝑠q⋅2s,其中𝑞q为奇数。
- 找到一个模𝑝p的非二次剩余𝑧z。
- 设置初始值𝑚=𝑠m=s,𝑐=𝑧𝑞c=zq,𝑡=𝑛𝑞t=nq,𝑟=𝑛(𝑞+1)/2r=n(q+1)/2。
- 重复以下步骤直到𝑡≡1 (mod 𝑝)t≡1 (mod p):
- 找到最小的非负整数𝑖i,使得𝑡2𝑖≡1 (mod 𝑝)t2i≡1 (mod p)。
- 更新𝑏=𝑐2𝑚−𝑖−1b=c2m−i−1,然后设置𝑚=𝑖m=i,𝑐=𝑏2c=b2,𝑡=𝑡⋅𝑐t=t⋅c,𝑟=𝑟⋅𝑏r=r⋅b。
最终,𝑟r即为方程的一个解。
C++实现示例
以下是Tonelli-Shanks算法的C++实现代码:
#include <iostream>
#include <vector>
#include <cassert>
using namespace std;
// 快速幂
long long power(long long a, long long b, long long p) {
long long res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
// 判断n是否为模p的二次剩余,p是奇质数
bool is_quadratic_residue(long long n, long long p) {
return power(n, (p - 1) / 2, p) == 1;
}
// Tonelli-Shanks算法求解x^2 ≡ n (mod p),p是奇质数
long long tonelli_shanks(long long n, long long p) {
if (n == 0) return 0;
if (p == 2) return n;
if (!is_quadratic_residue(n, p)) return -1; // n不是二次剩余,无解
long long q = p - 1;
long long s = 0;
while (q % 2 == 0) {
q /= 2;
++s;
}
if (s == 1) return power(n, (p + 1) / 4, p);
long long z;
for (z = 2; z < p; ++z) {
if (!is_quadratic_residue(z, p)) break;
}
long long m = s;
long long c = power(z, q, p);
long long t = power(n, q, p);
long long r = power(n, (q + 1) / 2, p);
while (t != 0 && t != 1) {
long long t2 = t;
long long i = 0;
for (; t2 != 1; ++i) {
t2 = t2 * t2 % p;
}
long long b = power(c, 1LL << (m - i - 1), p);
m = i;
c = b * b % p;
t = t * c % p;
r = r * b % p;
}
return r;
}
int main() {
long long n, p;
cout << "请输入n和p(奇质数):" << endl;
cin >> n >> p;
long long res = tonelli_shanks(n, p);
if (res == -1) {
cout << n << " 不是模 " << p << " 的二次剩余" << endl;
} else {
cout << n << " 模 " << p << " 的一个平方根是 " << res << endl;
}
return 0;
}
例题:洛谷 P5491【模板】二次剩余
题目要求求解一个数在模质数的情况下是否为二次剩余,并求出其平方根。以下是具体题目描述和代码实现:
题目描述:给定一个整数𝑛n和一个质数𝑝p,判断𝑛n是否是模𝑝p的二次剩余,并输出一个平方根。
代码实现
#include <iostream>
using namespace std;
long long power(long long a, long long b, long long p) {
long long res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
bool is_quadratic_residue(long long n, long long p) {
return power(n, (p - 1) / 2, p) == 1;
}
long long tonelli_shanks(long long n, long long p) {
if (n == 0) return 0;
if (p == 2) return n;
if (!is_quadratic_residue(n, p)) return -1;
long long q = p - 1;
long long s = 0;
while (q % 2 == 0) {
q /= 2;
++s;
}
if (s == 1) return power(n, (p + 1) / 4, p);
long long z;
for (z = 2; z < p; ++z) {
if (!is_quadratic_residue(z, p)) break;
}
long long m = s;
long long c = power(z, q, p);
long long t = power(n, q, p);
long long r = power(n, (q + 1) / 2, p);
while (t != 0 && t != 1) {
long long t2 = t;
long long i = 0;
for (; t2 != 1; ++i) {
t2 = t2 * t2 % p;
}
long long b = power(c, 1LL << (m - i - 1), p);
m = i;
c = b * b % p;
t = t * c % p;
r = r * b % p;
}
return r;
}
int main() {
long long n, p;
cin >> n >> p;
long long res = tonelli_shanks(n, p);
if (res == -1) {
cout << "Hola!" << endl; // 二次非剩余
} else {
cout << res << endl; // 输出平方根
}
return 0;
}
以上代码实现了洛谷 P5491 题目的求解过程,通过Tonelli-Shanks算法判断一个数是否为二次剩余并求出其平方根。如果数不是二次剩余,输出"Hola!",否则输出平方根。