二次剩余
\(\text{Problem}:\)【模板】二次剩余
\(\text{Solution}:\)
本文介绍 \(\text{Cipolla}\) 算法,下面默认模数 \(p\) 为奇质数。
定义:
非零数 \(n\) 是模 \(p\) 的二次剩余,当且仅当模 \(p\) 意义下方程 \(x^{2}\equiv n\) 有解。无解情况对应的 \(n\) 称为非二次剩余。
解的数量:
模 \(p\) 意义下的二次剩余有 \(\dfrac{p-1}{2}\) 个,非二次剩余有 \(\dfrac{p-1}{2}\) 个。
证明:
显然,如果模 \(p\) 意义下 \(x^{2}\equiv n\) 有解,那么当 \(x\in[1,\dfrac{p-1}{2}]\) 时可以取到所有 \(n\)。现在只需证明当 \(x\in[1,\dfrac{p-1}{2}]\) 时,\(x^{2}\text{ mod }p\) 互不相同。
假设存在 \(x,y\in[1,\dfrac{p-1}{2}],x\not=y\) 满足 \(x^{2}\equiv y^{2}\pmod p\),则有 \((x+y)(x-y)\equiv 0\pmod p\)。而 \(x-y\not=0,x+y\not=0\),故假设不成立,原命题得证。
欧拉判别准则:
若 \(n\) 是模 \(p\) 意义下的二次剩余,当且仅当 \(n^{\frac{p-1}{2}}\equiv1\pmod p\)。
若 \(n\) 是模 \(p\) 意义下的非二次剩余,当且仅当 \(n^{\frac{p-1}{2}}\equiv -1\pmod p\)。
证明:
由费马小定理,有 \(n^{p-1}\equiv 1\pmod p\),即 \((n^{\frac{p-1}{2}}-1)(n^{\frac{p-1}{2}}+1)\equiv 0 \pmod p\)。故有:
若 \(n\) 是模 \(p\) 意义下的二次剩余,有 \(n^{\frac{p-1}{2}}\equiv (x^{2})^{\frac{p-1}{2}}\equiv x^{p-1}\equiv 1\pmod p\)。
若 \(n^{\frac{p-1}{2}}\equiv 1\pmod p\),设 \(a\) 是 \(p\) 的一个原根,则存在 \(k\in[1,p-1]\),使得 \(n\equiv a^{k}\pmod p\),有:
即 \(p-1\mid \dfrac{k(p-1)}{2}\),得出 \(k\) 为偶数。令 \(j=\dfrac{k}{2},j\in \Z\),满足 \((a^{j})^2\equiv n\pmod p\),即 \(n\) 是模 \(p\) 意义下的二次剩余。
由以上证明,\(n\) 是模 \(p\) 意义下的二次剩余,与 \(n^{\frac{p-1}{2}}\equiv 1\pmod p\) 等价。原命题得证。
\(\text{Cipolla}\) 算法:
随机一个数 \(a\) 满足 \(a^{2}-n\) 是模 \(p\) 意义下的非二次剩余。期望约 \(2\) 次即可找到满足条件的 \(a\)。
定义 \(i^{2}\equiv a^{2}-n\pmod p\),使得所有数可以用 \(A+Bi\) 的形式表达(可以借助复数域理解)。
引理 \(1\):
证明:
引理 \(2\):
证明:
现在开始推导 \(n\) 的变换,有:
由于 \(p+1\) 是偶数,所以存在 \(x\),满足 \(x^2\equiv n\equiv (a+i)^{p+1}\pmod p\)。利用快速幂即可在 \(O(\log p)\) 的时间复杂度内求解。
\(\text{Code}:\)
#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std;
inline int read()
{
int s=0, w=1; ri char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
return s*w;
}
int n,P,I2;
inline int ksc(int x,int p) { int res=1; for(;p;p>>=1, x=1ll*x*x%P) if(p&1) res=1ll*res*x%P; return res; }
struct Node
{
int x,y;
};
inline Node operator + (Node a,Node b) { return (Node){a.x+b.x,a.y+b.y}; }
inline Node operator - (Node a,Node b) { return (Node){a.x-b.x,a.y-b.y}; }
inline Node operator * (Node a,Node b) { return (Node){(1ll*a.x*b.x%P+1ll*a.y*b.y%P*I2%P)%P,(1ll*a.x*b.y%P+1ll*a.y*b.x%P)%P}; }
inline bool Check(int x)
{
return ksc(x,(P-1)/2)==1;
}
inline int Rand()
{
int w=1ll*rand()*rand()%P;
w+=rand()-rand();
w=(w%P+P)%P;
return w;
}
inline Node KSC(Node x,int p)
{
Node res=(Node){1,0};
for(;p;p>>=1, x=x*x) if(p&1) res=res*x;
return res;
}
inline void Solve()
{
n%=P;
int a=0;
if(!n) { puts("0"); return; }
if(!Check(n)) { puts("Hola!"); return; }
while(Check((1ll*a*a%P-n+P)%P)) a=Rand();
I2=(1ll*a*a%P-n+P)%P;
int X1=KSC((Node){a,1},(P+1)/2).x;
int X2=P-X1;
if(X1>X2) swap(X1,X2);
if(X1==X2) printf("%d\n",X1);
else printf("%d %d\n",X1,X2);
}
signed main()
{
srand(time(NULL));
for(ri int T=read();T;T--)
{
n=read(), P=read();
Solve();
}
return 0;
}