二次剩余与 Cipolla 算法

二次剩余

对于 \(P,n\),若存在 \(x\),满足:

\[x^2≡n\pmod p \]

则称 \(n\) 为模 \(P\) 意义下的二次剩余。

勒让德符号

定义如下:

\[\left(\frac{n}{p}\right)= \begin{cases} 1,&n\text{ 在模 $p$ 意义下是二次剩余}\\ -1,&n\text{ 在模 $p$ 意义下是非二次剩余}\\ 0,&n\equiv0\pmod p \end{cases} \]

欧拉判别准则

对于勒让德符号,有:

\[\left({n\over p}\right)\equiv n^{p-1\over 2}\pmod{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\) 不存在。

定理

定理一:

\[n^2≡(p−n)^2\pmod p \]

证明:

把后面的平方展开即可。

定理二:

\(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 算法

分为三步:

  1. 判断给定的数 \(x\) 是否是二次剩余,如果不是就返回 \(−1\) 表示无解(如果是 \(0\) 的话直接返回 \(0\)

  2. 随机一个 \(a\),使其满足 \((a^2−x)\) 是二次非剩余(根据定理 \(2\),期望随机次数 \(2\) 次)

  3. 构造虚数 \(\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;
}

posted @ 2022-07-27 18:53  一粒夸克  阅读(120)  评论(0编辑  收藏  举报