二次剩余总结
为了方便,所有同余号都写成等号。
定义
如果对于 \(n\) , \(n\) 不是 \(p\) 的倍数, 且存在 \(x\) 使得 \(x^2 = n \mod p\) , 则称 \(n\) 为模 \(p\) 意义下的二次剩余, 如果 \(n\) 不是 \(p\) 的倍数, 却不是模 \(p\) 意义下的二次剩余,则 \(n\) 为模 \(p\) 意义下的非二次剩余。
性质
二次剩余的数量
结论 : 在模 \(p\) 意义下,有 \(\frac{p - 1}{2}\) 个二次剩余和 \(\frac{p - 1}{2}\) 个非二次剩余. (不包括 0)
对于一个二次剩余 \(n\) , 设 \(x^2=n\) 。
对于任意两个不相等的解 \(x_0, x_1\) , 有 \(x_0^2 = x_1^2\) , 移项有 \(x_0^2-x_1^2=(x_0+x_1)(x_0-x_1) = 0\) .
因为 \(x_0 \neq x_1\) , 所以 \(x_0 - x_0 \neq 0\), 所以 \(x_0 + x_1 = 0\) , 所以任意一个二次剩余的解是一对相反数。当 \(p\) 为奇素数时两个相反数不会相等。因为奇偶性不同。
所以任意一对相反数都一一对应一个二次剩余。所以二次剩余数量为 \(\frac{p-1}{2}\) , 相对应的非二次剩余数量为 \((p - 1) - \frac{p-1}{2} = \frac{p - 1}{2}\)
欧拉判别准则
若 \(n\) 是二次剩余,当且仅当 \(n^{\frac{p-1}{2}} = 1\)
若 \(n\) 是非二次剩余,当且仅当 \(n^{\frac{p-1}{2}} = -1\)
证明:
因为 \(n^{p-1}=1\) , 由于 \(p\) 是奇素数, 所以有 \(n^{p - 1}- 1 = (n^{\frac{p-1}{2}} + 1)(n^{\frac{p-1}{2}} -1 )= 0\) , 所以 \(n^{\frac{p-1}{2}} = \pm 1\)
若 \(n\) 是二次剩余则 \(n^{\frac{p-1}{2}} = (x^2)^{\frac{p-1}{2}}=x^{p-1}=1\)
若 \(n^{\frac{p-1}{2}}=1\) , 则将 \(n\) 表示为 \(g^k\) , 那么有 \(g^{k\frac{p-1}{2}} = 1\) , 根据原根的阶是 \(\varphi(p)\) , 有 \((p - 1)| k\frac{p-1}{2}\) , 所以 \(2 | k\) , 所以 \(n\) 开根为 \(g^{\frac{k}{2}}\) , \(n\) 是二次剩余。
那么 \(n\) 是非二次剩余等价于 \(n^\frac{p-1}{2}=-1\)
Cipolla 算法
用来在模 \(p\) ( \(p\) 是质数) 意义下,对一个二次剩余开根
找到一个 \(a\) 使得 \(a^2 - n\) 是非二次剩余。 因为非二次剩余一共有 \(\frac{p - 1}{2}\) 个,所以期望 \(2\) 次就能找到。
考虑拓域, 设 \(i^2 = a^2 - n\) , 每个数都能表示成 \(A +Bi\) .
有定理 \((a+i)^{p+1} = n\)
引理 1:
\(i^p=-i\)
证明:
\(i^p = (i^2)^{\frac{p-1}{2}}\cdot i = (a^2 - n)^{\frac{p-1}{2}}\cdot i = -i\)
引理 2:
当 \(p\) 是素数时
因为 \(p\) 是素数, 且 \(k \neq 0,k\neq p\) , 所以 \(\tbinom{p}{k}\) 分子中的 \(p\) 不会被分母中的数整除。所以 \(\tbinom{p}{k}\) 为 \(0\)
引理 3:
\((a+b)^p = a^p + b^p\)
证明:
\((a+b)^p = \sum_{i=0}^{p} \tbinom{p}{i}a^pb^{p-i} = a^p + b^p\)
证明 :
既然 \((a+i)^{p+1} = n\) , 那么显然 \((a+i)^{\frac{p+1}{2}} = \sqrt{n}\) 。 所以 \(n\) 的平方根为 \((a+i)^{\frac{p+1}{2}}\) 和 \(p-(a+i)^{\frac{p+1}{2}}\)
代码
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
int T;
int n, mod;
int I;
struct Complex {
int x, y;
Complex (int _x = 0, int _y = 0) {
x = _x, y = _y;
}
friend Complex operator + (Complex &a, Complex &b) {
return Complex((a.x + b.x) % mod, (a.y + b.y) % mod);
}
friend Complex operator * (Complex &a, Complex &b) {
return Complex((a.x * b.x % mod + a.y * b.y % mod * I % mod) % mod, (a.x * b.y % mod + a.y * b.x % mod) % mod);
}
friend Complex operator % (Complex &a, int b) {
return Complex(a.x % b, a.y % b);
}
};
Complex qpow(Complex a, int b) {
Complex res(1, 0);
while (b) {
if (b & 1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
signed main() {
scanf("%lld", &T);
while (T--) {
scanf("%lld%lld", &n, &mod);
if (qpow(n, (mod - 1) / 2) == mod - 1) {
puts("Hola!");
continue;
}
if (n == 0) {
puts("0");
continue;
}
int a = rand() % mod;
while (!a || qpow((a * a % mod + mod - n) % mod, (mod - 1) / 2) != mod - 1) {
a = rand() % mod;
}
I = (a * a % mod + mod - n) % mod;
int ans1 = qpow(Complex(a, 1), (mod + 1) / 2).x % mod;
int ans2 = mod - ans1;
if (ans1 == ans2) {
printf("%lld\n", ans1);
}
else {
printf("%lld %lld\n", min(ans1, ans2), max(ans1, ans2));
}
}
return 0;
}