二次剩余与 Cipolla 算法
二次剩余
对于 \(P,n\),若存在 \(x\),满足:
则称 \(n\) 为模 \(P\) 意义下的二次剩余。
勒让德符号
定义如下:
欧拉判别准则
对于勒让德符号,有:
证明:
若 \(n≡0\pmod p\),显然该式子成立。
若 \(n\) 在模 \(p\) 意义下是二次剩余,即存在 \(x^2≡n\pmod p\),那么就有 \(x^{p−1}≡1\pmod p\),根据费马小定理显然 \(x\) 存在。
若 \(n\) 在模 \(p\) 意义下是二次非剩余,我们仍假设存在 \(x^2≡n\pmod p\),从而有 \(x^{p−1}≡−1\pmod p\),由费马小定理,显然 \(x\) 不存在。
定理
定理一:
证明:
把后面的平方展开即可。
定理二:
\(p\) 的二次剩余和二次非剩余的个数均为 \(\dfrac{p−1}{2}\)(不考虑 \(0\)的情况下)。
证明:
我们只考虑所有的 \(\frac{n}{2}\),假设有 \(x≠y\) 且 \(x^2≡y^2\pmod p\),则 \(p∣(x^2−y^2)\),即 \(p∣(x−y)(x+y)\),显然 \(p∤(x−y)\),则 \(p∣(x+y)\),故 \(x+y≡0\pmod p\),就是定理一的情况。
也就是说不同的 \(x^2\) 共有 \(\dfrac{p−1}{2}\),二次剩余也为\(\dfrac{p−1}{2}\),减一减就可知二次非剩余个数也是 \(\dfrac{p−1}{2}\) 。
原根法求二次剩余
对于一个 \(P\) ,求出其原根 \(g\) ,用 \(\text{BSGS}\) 可以在 \(O(\sqrt{n} \log n)\) 的时间内求出 \(a=g^{k}\) ,如果存在原根,那么 \(k \bmod 2=0\) ,答案就是 \(g^{\frac{k}{2}} \bmod P\) 。
inline int solve(int x) {
if(!x) return 0;
G = GetG();
int s = sqrt(md - 1) + 1;
map<int,int> mp;
for (int i = 0,t = 1; i <= s; i++) {
if(!mp.count(t)) mp[t] = i;
t = 1ll * t * G % md;
}
int T = pwr(pwr(G, md - 2), s);
int res = 1e9;
for (int i = 0, t = x; i <= s; i++) {
if(mp.count(t)) res = min(res, i * s + mp[t]);
t = 1ll * t * T % md;
}
if(res & 1) return -1;
return min(pwr(G, res/2), (md - pwr(G, res/2)) % md);
}
更快的方法:Cipolla 算法
分为三步:
-
判断给定的数 \(x\) 是否是二次剩余,如果不是就返回 \(−1\) 表示无解(如果是 \(0\) 的话直接返回 \(0\))
-
随机一个 \(a\),使其满足 \((a^2−x)\) 是二次非剩余(根据定理 \(2\),期望随机次数 \(2\) 次)
-
构造虚数 \(\omega=\sqrt{y^{2}-a}\) ,那么答案就是 \(x=\sqrt{y^{2}-\omega^{2}}\) ,然后构造复数 \((\alpha, \beta)=\alpha+\beta \omega\) ,求出 \(x=(y, 1)^{\frac{(p+1)}{2}}\) ,模拟复数乘法即可
可以证明结果没有虚部,就是答案
inline bool check(int x){
return pwr(x, (md - 1)/2) == 1;
}
inline int solve(int x){
if(!x) return 0;
if(!check(x)) return -1;
int y = 1;
while(check((1ll * y * y - x + md) % md)) y = 1ll * rand() * rand() % md;
int T = (1ll * y * y - x + md) % md;
int resx = 1, resy = 0, tmpx = y, tmpy = md - 1;
for(int i = (md + 1)/2; i; i >>= 1){
if(i & 1){
int x = resx, y = resy;
resx = (1ll * x * tmpx % md + 1ll * T * y % md * tmpy) % md;
resy = (1ll * x * tmpy % md + 1ll * y % md * tmpx) % md;
}
int x = tmpx, y = tmpy;
tmpx = (1ll * x * x % md + 1ll * T * y % md * y) % md;
tmpy = (1ll * x * y % md + 1ll * y % md * x) % md;
}
return min(resx, (md - resx) % md);
}
P5491 【模板】二次剩余
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,md;
inline int pwr(int x,int y){
int res=1;
while(y){
if(y&1)res=1ll*res*x%md;
x=1ll*x*x%md;y>>=1;
}
return res;
}
inline bool check(int x){
return pwr(x,(md-1)/2)==1;
}
inline int solve(int x){
if(!x)return 0;
if(!check(x))return -1;
int y=1;
while(check((1ll*y*y-x+md)%md))y=1ll*rand()*rand()%md;
int T=(1ll*y*y-x+md)%md;
int resx=1,resy=0,tmpx=y,tmpy=md-1;
for(int i=(md+1)/2;i;i>>=1){
if(i&1){
int x=resx,y=resy;
resx=(1ll*x*tmpx%md+1ll*T*y%md*tmpy)%md;
resy=(1ll*x*tmpy%md+1ll*y%md*tmpx)%md;
}
int x=tmpx,y=tmpy;
tmpx=(1ll*x*x%md+1ll*T*y%md*y)%md;
tmpy=(1ll*x*y%md+1ll*y%md*x)%md;
}
return min(resx,(md-resx)%md);
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&md);
int res=solve(n);
if(res==-1)puts("Hola!");
else if(!res)puts("0");
else printf("%d %d\n",res,(md-res)%md);
}
return 0;
}