二次剩余
二次剩余
若方程 \(x^k \equiv a \pmod{m}\) 有解,则称 \(a\) 是模 \(m\) 的 \(k\) 次剩余,否则称 \(a\) 是模 \(m\) 的 \(k\) 次非剩余。下面只讨论 \(k=2\) 的情况,即二次剩余(平方剩余)。
先提出几个关键的定理(下面的 \(p\) 均为奇质数):
定理 1:若 \(a\) 是模 \(p\) 的 \(k\) 次剩余,\(b\) 是模 \(p\) 的 \(k\) 次非剩余,则 \(ab\) 是模 \(p\) 的 \(k\) 次非剩余。
证明:若 \(ab\) 是模 \(p\) 的 \(k\) 次剩余,可以设 \(x_1^k \equiv a \pmod{p}, x_2^k \equiv ab \pmod{p}\),于是 \(x_2^k \equiv x_1^k b \pmod{p}\)。
-
若 \(x_1 \equiv 0 \pmod{p}\),则 \(ab \equiv 0 \pmod{p}\),与 \(ab\) 是 \(k\) 次非剩余矛盾。
-
若 \(x_1 \not\equiv 0 \pmod{p}\),则 \((x_2x_1^{-1})^k \equiv b \pmod{p}\)(\(p\) 为奇质数,\(x_1^{-1}\) 一定存在)。这与 \(b\) 是 \(k\) 次非剩余矛盾。
\(\square\)
定理 2:在模 \(p\) 的简化剩余系中,二次剩余和二次非剩余各占 \(\dfrac{p-1}{2}\)。
证明:设 \(p \nmid x\)(简化剩余系),于是 \(x\) 可以写成 \(pq+r\),其中 \(q,r\) 为整数,且 \(|r| \le \dfrac{p-1}{2}\)。
于是得出 \(x^2 = (pq+r)^2 \equiv r^2 \pmod{p}\)。
发现 \(\forall 1 \le s < t \le \dfrac{p-1}{2}: t^2 - s^2 = (t-s) (t+s)\),其中 \(1 \le t+s,t+s \le p-1\),于是 \((t-s) \not\equiv 0 \pmod{p},(t+s) \not\equiv 0 \pmod{p}\),由于 \(p\) 为奇质数,于是 \((t-s) (t+s) \not\equiv 0 \pmod{p}\)。
即 \(\forall 1 \le s < t \le \dfrac{p-1}{2}: t^2 - s^2 \not\equiv 0 \pmod{m}\)。
这说明若 \(x^2 \equiv a \pmod{m}\) 有解,设其中任意两解为 \(s,t\),则有 \(s^2 - t^2 \equiv 0 \pmod{p}\),而上面说明了这只能在 \(s = -t\) 或 \(s = t\) 时成立。
于是 \(1^2, 2^2, \dots, \left(\dfrac{p-1}{2}\right)^2\) 代表了模 \(p\) 的全部二次剩余,其余为模 \(p\) 的二次非剩余。
\(\square\)
定理 3:模 \(p\) 的两个二次非剩余的乘积是模 \(p\) 的二次剩余。
证明:设 \(a,b\) 为模 \(p\) 的两个二次非剩余。根据 定理 1 和 定理 2 可得,\(1^2a, 2^2 a,\dots, \left(\dfrac{p-1}{2}\right)^2 a\) 均为二次非剩余。且 \(ab\) 和它们模 \(p\) 两两不同余,于是 \(ab\) 只能为二次剩余。
\(\square\)
勒让德符号
勒让德符号可以方便我们研究二次剩余:
定义(勒让德符号):
根据 定理 3 可以得出,\(\left(\dfrac{ab}{p}\right) = \left(\dfrac{a}{p}\right) \left(\dfrac{b}{p}\right)\)。
Euler 判别条件
判定二次剩余只需要求出对应的勒让德符号的值,接下来引入 Euler 判别条件,用于求解 \(p\) 为奇质数时的勒让德符号。
利用二次互反律的方法在 Competive Programming 中不优于 Euler 判别条件,故不作介绍。
定理 5(Euler 判别条件):设 \(a \in \mathbb{Z}\),\(p\) 为奇质数,则 \(a^{\frac{p-1}{2}} \equiv \left(\dfrac{a}{p}\right) \pmod{p}\)。
证明:若 \(p \mid a\),则 \(\left(\dfrac{a}{p}\right) \equiv a^{\frac{p-1}{2}} \equiv 0 \pmod{p}\)。于是不妨设 \(p \nmid a\)。
若 \(\left(\dfrac{a}{p}\right) = 1\),则 \(\exists x \in \mathbb{Z}: x^2 \equiv a \pmod{p}\),于是 \(a^{\frac{p-1}{2}} \equiv x^{p-1} \equiv 1 \pmod{p}\)(\(p\) 为奇质数,由费马小定理可得)。
若 \(\left(\dfrac{a}{p}\right) = -1\)。则 \(1^2, 2^2, \dots, \left(\dfrac{p-1}{2}\right)^2\) 代表了模 \(p\) 的所有二次剩余,且 \(1^2a, 2^2a, \dots, \left(\dfrac{p-1}{2}\right)^2a\) 代表了模 \(p\) 的所有二次非剩余(由 定理 2 可得),于是它们两两不同余,构成了 \(p\) 的一个简化剩余系。
于是 \(\prod\limits_{i=1}^{\frac{p-1}{2}} i^2 ai^2 \equiv \prod\limits_{r=1}^{n-1} r \equiv -1 \pmod{p}\)。即 \(a^{\frac{p-1}{2}}\left(\prod\limits_{i=1}^{\frac{p-1}{2}} i^2\right)^2 \equiv -1 \pmod{p}\)。注意到左边后面一块带平方,于是里面的累乘可以不管符号,不妨把 \(i^2\) 改写成 \(-i^2\),于是有 \(a^{\frac{p-1}{2}}\left(\prod\limits_{i=1}^{\frac{p-1}{2}} (p-i)i\right)^2 \equiv -1 \pmod{p}\),即 \(a^{\frac{p-1}{2}}(p-1)!^2 \equiv -1 \pmod{p}\),由于 \((p-1)! \equiv -1 \pmod{p}\),于是 \(a^{\frac{p-1}{2}}\equiv -1 \pmod{p}\)
\(\square\)
Cipolla 算法
Cipolla 算法可以求解 \(x^2 \equiv n \pmod{p}\)(\(p\) 为奇质数,\(x\) 是模 \(p\) 的二次剩余,且 \(p \nmid n\)),算法流程如下。
- 随机寻找一个 \(a\),满足 \(a^2 - n\) 是模 \(p\) 的二次非剩余。
- 令 \(i^2 \equiv a^2 - n \pmod{p}\),这里由于 \(a^2 - n\) 是模 \(p\) 的二次非剩余,需要扩域计算(类似复数)。
- 求出 \((a+i)^{\frac{p+1}{2}}\) 即为方程的一个解。
通过证明以下引理来证明算法的时间复杂度和正确性:
引理 1:第一步期望 \(2\) 次随机即可找到需要的二次非剩余。
证明:列出关于 \(x\) 的方程 \(x^2 \equiv a^2 - n \pmod{p}\),第一步即找出一个 \(a\),使得这个方程无解。
移项可得 \(a^2 - x^2 \equiv n \pmod{p}\),分解平方差:\((a-x) (a+x)\equiv n \pmod{p}\)。
由于 \(p \nmid n\),于是等号能成立当且仅当 \(a-x \not\equiv 0 \pmod{p}\) 且 \(a+x \not\equiv 0 \pmod{p}\)。
在模 \(p\) 意义下,能满足 \(bc \equiv n \pmod{p}\) 的 \((b,c)\) 恰有 \(p-1\) 对,即 \(b = 1,2,\dots,p-1,c = b^{-1}\)。将 \(a-x\) 视为 \(b\),\(a+x\) 视为 \(c\),则 \((a-x,a+x)\) 有 \(p-1\) 种。
不难发现,一个使方程有正整数解的 \(a\) 对应了两种不同的解 \(x\)(\(x\) 是一个解,则 \(-x\) 也是一个解),又因为 \(n\) 是二次剩余,于是使得方程有解 \(x=0\) 的 \(a\) 恰有两个,而按照上面的讨论方式,\(a-x = a+x\) 对应了两种情况。于是使得方程有解的 \(a\) 共有 \(\dfrac{p+1}{2}\) 个,剩下的 \(\dfrac{p-1}{2}\) 种 \(a\) 都会使方程无解。
当 \(p\) 足够大时,可以认为使得方程有解和无解的概率各占 \(\dfrac{1}{2}\)。
\(\square\)
引理 2:\((a+i)^{p+1} \equiv n \pmod{p}\)。
证明:拆开:\((a+i)^{p+1} = (a+i)^p (a+i)\)。
对前面一项用二项式定理展开,注意到 \(\forall 1 \le i \le p-1:\dbinom{p}{i} \equiv 0 \pmod{p}\)(由 \(p\) 是奇质数容易得到)。于是 \((a+i)^p \equiv a^p + i^p \pmod{p}\)。
由于 \(a^2 - n\) 是模 \(p\) 的二次非剩余,于是根据 定理 5(Euler 判别条件)可得 \((a^2 - n)^{\frac{p-1}{2}} \equiv -1 \pmod{p}\),即 \(i^{p-1} \equiv -1 \pmod{p}\),由费马小定理得 \(a^p \equiv a \pmod{p}\)。
于是 \((a^p+i^p) (a+i) \equiv (a-i) (a+i) \equiv a^2 - i^2 \equiv a^2 - a^2 + n \equiv n \pmod{p}\)。
\(\square\)
引理 3:\((a+i)^{\frac{p+1}{2}}\) 在模 \(p\) 意义下的结果不含 \(i\)。
证明:若存在一个 \(b+ci\),满足 \(c \not\equiv 0 \pmod p,(b+ci)^2 \equiv n \pmod{p}\),则 \(b^2 + 2bci + c^2(a^2 - n) \equiv n \pmod{p}\)。
把“实部”和“虚部”分别放到两边:\(b^2 + c^2(a^2 - n) -n \equiv - 2bci \pmod{p}\),类似复数,等式成立当且仅当左右两边均为 \(0\)。考虑右边的“虚部”:由于 \(c \not\equiv 0 \pmod p\),那么一定有 \(b \equiv 0 \pmod p\),那么 \((ci)^2 = c^2(a^2 - n) \equiv n \pmod{p}\),于是 \(a^2 - n \equiv n(c^{-1})^2 \pmod{p}\)。由于 \(n\) 是二次剩余,\((c^{-1})^2\) 显然是二次剩余,于是 \(a^2 - n\) 是二次剩余。
这和算法第一步要求的 \(a^2 - n\) 是二次非剩余矛盾。
\(\square\)
于是 Cipolla 算法可以在期望 \(O(\log p)\) 的的时间内求解方程 \(x^2 \equiv n \pmod{p}\)。
实现
#include <bits/stdc++.h>
using namespace std;
struct Complex {
Complex() {r=i=0;};
Complex(int v) {r=v;i=0;}
Complex(int r,int i):r(r),i(i) {}
int r,i;
};
int sq,mod;
mt19937 rng(random_device{}());
Complex operator+(const Complex &a,const Complex &b) {return Complex((a.r+b.r)%mod,(a.i+b.i)%mod);}
Complex operator*(const Complex &a,const Complex &b) {
return Complex((1ll*a.r*b.r%mod+1ll*a.i*b.i%mod*sq%mod)%mod,(1ll*a.r*b.i%mod+1ll*a.i*b.r%mod)%mod);
}
Complex qpow(Complex a,int b) {
Complex res=1;
for(;b;b>>=1,a=a*a) {
if(b&1) res=res*a;
}
return res;
}
int qpow(int a,int b,int mod) {
int res=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) res=1ll*res*a%mod;
return res;
}
bool check(int n,int p) {return n?qpow(n,(p-1)/2,p)==1:true;}
int Cipolla(int n,int p) {
mod=p; n%=mod;
if(n==0) return 0;
uniform_int_distribution<int> gen(0,p-1);
int a=gen(rng);
while(check((1ll*a*a%mod+mod-n)%mod,p)) a=gen(rng);
sq=(1ll*a*a%mod+mod-n)%mod;
return qpow(Complex(a,1),(p+1)/2).r;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int T=0; cin>>T;
while(T--) {
int n,p; cin>>n>>p;
if(!check(n,p)) cout<<"Hola!"<<"\n";
else {
int res=Cipolla(n,p);
if(res>p-res) res=p-res;
cout<<res;
if(res) cout<<" "<<p-res;
cout<<"\n";
}
}
return 0;
}