浅谈二次剩余
浅谈二次剩余
定义:对于正整数\(p,n\),若存在\(x\)使得\(x^2 \equiv n(\bmod\ p)\).则称\(n\)是模\(p\)的二次剩余。(在本文中我们只考虑\(p\)为奇质数的情况)
勒让德括号,欧拉判别准则
下面引入勒让德括号,它可以简化讨论:
定理(欧拉判别准则): 当\(p\)为奇素数时,\(\left( \frac{n}{p}\right) \equiv n^{\frac{p-1}{2}} (\bmod\ p)\)
证明: \(p|n\)的情况是显然的,我们考虑\(p \nmid n\)时的情况。显然\(n^{\frac{p-1}{2}}=\plusmn 1\).若\(n\)是二次剩余,则\(n^{\frac{p-1}{2}}=(x^2)^{\frac{p-1}{2}}=x^{p-1}=1\). 若\(n^{\frac{p-1}{2}}=1\),把\(n\)用模\(p\)下的原根\(g\)表示为\(n=g^k\),那么\(g^{k\frac{p-1}{2}}=1\).又因为\(g^{p-1}=1\),由原根的性质(见此处定理3.1)得到\(p-1|k\frac{p-1}{2}\),那么\(k\)必定为偶数,令\(x=g^{\frac{k}{2}}\)即可,说明\(n\)是二次剩余。也就是说 \(n^{\frac{p-1}{2}}=1\)是\(n\)是二次剩余的充要条件。反之-1的情况成立,上面的\(-1\)在模意义下其实就是\(p-1\)。
从这个定理我们已经得到二次剩余的一种求法,求出原根\(g\)后用BSGS求出\(g^k=n\)的解,那么\(g^{\frac{k}{2}}\)就是一个解。复杂度\(O(\sqrt{p})\)
Cipolla算法
上面这个\(O(\sqrt{p})\)的算法还不够优秀,我们用Cipolla算法来求解\(x^2 \equiv n(\bmod\ p)\)。算法流程如下:
- 不断随机\(a\),直到找到一个\(a\)使得\(a^2-n\)是非二次剩余(用欧拉判别准则判断)(我们之后会证明期望随机次数为2).
- 定义\(\omega^2=a^2-n\).但是我们知道\(a^2-n\)是非二次剩余,那么我们可以类比复数域对无法开根的数\(-1\)定义\(\mathrm{i}^2=-1\),我们把所有数表示为\(p+q\omega\)的形式,运算规则类似复数。那么\(x=\plusmn (a+\omega)^{\frac{p+1}{2}}\).
引理1:\(x^2 \equiv n(\bmod\ p)\)有且仅有两组解,且满足\(x_1+x_2 \equiv n(\bmod\ 0)\)
证明:假设对于\(x_1 \neq x_2\)有\(x_1^2 \equiv x_2^2\),移项得\((x_1-x_2)(x_1+x_2)\equiv 0\)。 这很类似实数的开根。又因为每个\(n\)都对应两个不同的\(x_1,x_2\),一共只有\(p-1\)个数,那么二次剩余的数量恰为\(\frac{p-1}{2}\).那么随机到非二次剩余的概率为\(\frac{p-1}{2p} \approx \frac{1}{2}\).期望次数为\(\sum_{i=0}\frac{i}{2^i}=2-\frac{N+2}{2^N}\),趋近于2. 这样算法的复杂度就是\(O(\log p)\)
引理2:$ \omega^{p} \equiv-\omega$
证明: \(\omega^{p} \equiv \omega\left(\omega^{2}\right)^{\frac{p-1}{2}} \equiv \omega\left(a^{2}-n\right)^{\frac{p-1}{2}} \equiv-\omega\)
引理3:\((A+B)^{p} \equiv A^{p}+B^{p}\)
证明:根据二项式定理,由于\(p\)是质数,除了\(C_{p}^{0}, C_{p}^{p}\) 外的組合数分子上的阶乘没法消掉\(p\),模\(p\)都为\(0,\) 只有\(C_{p}^{0} A^{0} B^{p}+C_{p}^{p} A^{p} B^{0}\)
根据引理2,3:
那么\(x=\plusmn (a+i)^{\frac{p+1}{2}}\)(可以用反证法证明虚部为0,这里不再赘述)
struct com { //cipolla用的类似复数的东西
ll real;
ll imag;//a+b*sqrt(w)
com() {
}
com(ll _real,ll _imag) {
real=_real;
imag=_imag;
}
};
inline com mul(com a,com b,ll w) {//重载乘法
return com((a.real*b.real%mod+a.imag*b.imag%mod*w%mod)%mod,(a.real*b.imag%mod+a.imag*b.real%mod)%mod);
}
inline com fpow(com x,ll k,ll w) {
com ans=com(1,0);
while(k) {
if(k&1) ans=mul(ans,x,w);
x=mul(x,x,w);
k>>=1;
}
return ans;
}
inline ll cipolla(ll x) {
if(fast_pow(x,(mod-1)/2)==mod-1) return -1;//x不存在二次剩余
while(1) {
ll a=((rand()<<15)|rand())%mod;
ll w=(a*a%mod-x+mod)%mod;
if(fast_pow(w,(mod-1)/2)==mod-1) { //x-a^2不存在二次剩余
return fpow(com(a,1),(mod+1)/2,w).real%mod;
}
}
return -1;
}
完整代码:LuoguP5491
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef long long ll;
int T;
ll n,mod;
inline ll fast_pow(ll x,ll k) {
ll ans=1;
while(k) {
if(k&1) ans=ans*x%mod;
x=x*x%mod;
k>>=1;
}
return ans;
}
inline ll inv(ll x) {
return fast_pow(x,mod-2);
}
struct com { //cipolla用的类似复数的东西
ll real;
ll imag;//a+b*sqrt(w)
com() {
}
com(ll _real,ll _imag) {
real=_real;
imag=_imag;
}
};
inline com mul(com a,com b,ll w) {
return com((a.real*b.real%mod+a.imag*b.imag%mod*w%mod)%mod,(a.real*b.imag%mod+a.imag*b.real%mod)%mod);
}
inline com fpow(com x,ll k,ll w) {
com ans=com(1,0);
while(k) {
if(k&1) ans=mul(ans,x,w);
x=mul(x,x,w);
k>>=1;
}
return ans;
}
inline ll cipolla(ll x) {
if(fast_pow(x,(mod-1)/2)==mod-1) return -1;//x不存在二次剩余
while(1) {
ll a=((rand()<<15)|rand())%mod;
ll w=(a*a%mod-x+mod)%mod;
if(fast_pow(w,(mod-1)/2)==mod-1) { //x-a^2不存在二次剩余
return fpow(com(a,1),(mod+1)/2,w).real%mod;
}
}
return -1;
}
int main() {
scanf("%d",&T);
while(T--){
scanf("%lld %lld",&n,&mod);
if(n==0){
puts("0");
continue;
}
ll ans=cipolla(n);
if(ans==-1) puts("Hola!");
else{
ans=(ans%mod+mod)%mod;
printf("%lld %lld\n",min(ans,mod-ans),max(ans,mod-ans));
}
}
}