二次剩余学习笔记
二次剩余解决的是 \(x^2 = n \pmod p\),求 \(x\) 的解问题,就是在模意义下的开根。
这里介绍 \(p\) 为奇质数的情况。
解的数量
考虑 \(x^2 = n \pmod p\) 如果有两个不同的解 \(x_1\) 和 \(x_2\)。
因为 \(x_1 \neq x_2\),所以:
因此两个解一定互为相反数。
因此对于一个只有两个解。
对于每一个 \(p\),有解的 \(n\) 有多少?
考虑枚举 \(x \in [1, \frac{p - 1}{2}]\) 的所有数,\(x^2\) 是互不相同的;对于 \([\frac{p + 1}{2}, p - 1]\) 的都已经被前面的他的相反数算过了。
所以模 \(p\) 意义下是二次剩余的数的个数是 \(\frac{p - 1}{2}\)。
欧拉准则
欧拉准则用于判断一个数是否是二次剩余?
由于费马小定理,\(n^{p - 1} \equiv 1 \pmod 1\)。
由于 \(1\) 开根后一定是 \(-1\) 或 \(1\),所以 \(n^{\frac{p-1}{2}}\) 只能是 \(1\) 或 \(-1\)。
如果 \(n\) 是二次剩余,那么 \(n\) 可以表示成 \(k^2\),所以 \(n^{\frac{p - 1}{2}} \equiv k^{p - 1} \equiv 1 \pmod p\)。
如果 \(n^{\frac{p - 1}{2}} = 1\),那么找到一个 \(p\) 的原根 \(g\),设 \(n = g^k\),那么 \(g^{\frac{k (p - 1)}{2}} \equiv 1\)。又因为 \(g\) 是原根,所以只有 \(\frac{k(p - 1)}{2}\) 是 \(p - 1\) 的倍数时才满足 \(n^{\frac{p - 1}{2}} = 1\)。于是 \(k\) 是 \(2\) 的倍数。
所以 \(g^{\frac{k}{2}}\) 是 \(n\) 的二次剩余。
所以 \(n\) 是二次剩余当且仅当 \(n^{\frac{p - 1}{2}} \equiv 1 \pmod p\)。
Cipolla 定理
重新回到 \(x^2 = n \pmod p\) 这个问题。
找到一个 \(a\) 满足 \(a^2 - n\) 是非二次剩余。
随机 + 检测可在期望 \(2\) 次的时间内可以找到一个 \(a\)。
定义一个新的根 \(i\) 表示 \(i^2 = a^2 - n\) 。
引理1 :\(i^p = -i\)
证明:\(i^p = i \times (i^2)^{\frac{p - 1}{2}} = i \times a^{\frac{p - 1}{2}} = -i\)
引理2 :\((A + B) ^ p = A^p + B^p\)
证明:\((A + B)^p = \sum\limits_{i = 0}^{p} \binom{p}{i} A^i B^{p-i}\)
考虑 \(\binom{p}{i} = \frac{p}{i! (p - i)!}\) 在 \(i \in [1, p - 1]\) 的情况,\(i! (p-i)!\) 不含 \(p\) 这个因子而 \(p!\) 含,因此 \(\binom{p}{i} \equiv 0 \pmod p\) 。于是得证。
因此 \((a + i)^{p + 1} = (a+i) (a^p + i^p) = (a + i) (a - i) = a^2 - i^2 = a^2 - (a^2 - n) = n\) 。
所以我们就得到了 \(x^2 = n \pmod p\) 的一个解: \((a+i)^{\frac{p + 1}{2}}\)
代码
#include<bits/stdc++.h>
#define L(i, j, k) for(int i = j, i##E = k; i <= i##E; i++)
#define R(i, j, k) for(int i = j, i##E = k; i >= i##E; i--)
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define db double
#define mkp make_pair
using namespace std;
const int N = 3e5 + 7;
int n, mod, ipow;
struct CP {
int x, y;
CP (int xx = 0, int yy = 0) {
x = xx, y = yy;
}
};
CP operator * (CP aa, CP bb) {
return CP(((ll) aa.x * bb.x + (ll) aa.y * bb.y % mod * ipow % mod) % mod, ((ll) aa.x * bb.y + (ll) aa.y * bb.x) % mod);
}
CP CPpow(CP x, int y) {
CP res(1, 0);
for(; y; x = x * x, y >>= 1) if(y & 1) res = res * x;
return res;
}
int qpow(int x, int y = mod - 2) {
int res = 1;
for(; y; x = (ll) x * x % mod, y >>= 1) if(y & 1) res = (ll) res * x % mod;
return res;
}
bool check(int x) { return qpow(x, (mod - 1) / 2) == 1; }
int a;
void Main() {
cin >> n >> mod;
if(n == 0) return cout << "0\n", void();
if(!check(n)) return cout << "Hola!\n", void();
a = rand() % mod;
while(check(((ll) a * a % mod + mod - n) % mod)) a = rand() % mod;
ipow = ((ll) a * a % mod + mod - n) % mod;
int res = CPpow(CP(a, 1), (mod + 1) / 2).x;
res = min(res, mod - res);
if(res == mod - res) cout << res << endl;
else cout << res << " " << mod - res << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
srand(19260817);
int T; cin >> T;
while(T--) Main();
return 0;
}
参考资料:kewth 二次剩余