这里就简单记一下二次剩余的一些东西,部分内容没有证明(有些我也不太会)。
定义 1 1 1:设素数 p > 2 , d ∈ Z , p ∤ d p>2,d∈\mathbb Z,p \nmid d p>2,d∈Z,p∤d,若 x 2 ≡ d ( m o d p ) x^2\equiv d \pmod p x2≡d(modp) 有解,称 d d d 是模 p p p 的二次剩余,否则称 d d d 是模 p p p 的二次非剩余。
定理 1 1 1:在模 p p p 的一个既约剩余系中,恰有 p − 1 2 \frac{p-1}{2} 2p−1 个模 p p p 的二次剩余, p − 1 2 \frac{p-1}{2} 2p−1 个模 p p p 的二次非剩余。若 d d d 是模 p p p 的二次剩余,则 x 2 ≡ d ( m o d p ) x^2\equiv d \pmod p x2≡d(modp) 的解数为 2 2 2。
定理 2 2 2(Euler 判别法):设素数 p > 2 p>2 p>2, p ∤ d p\nmid d p∤d,那么:
- d d d 是模 p p p 的二次剩余的充分必要条件是:
d p − 1 2 ≡ 1 ( m o d p ) d^{\frac{p-1}{2}}\equiv1\pmod p d2p−1≡1(modp)
- d d d 是模 p p p 的非二次剩余的充分必要条件是:
d p − 1 2 ≡ − 1 ( m o d p ) d^{\frac{p-1}2}\equiv-1\pmod p d2p−1≡−1(modp)
Cipolla 算法
求解关于 x x x 的同余方程:
x 2 ≡ n ( m o d p ) x^2\equiv n\pmod p x2≡n(modp)
当 p = 2 p=2 p=2 是很好解,接下来只考虑 p > 2 p>2 p>2 的奇素数的情况。
算法流程是这样的:
- 找到 a a a 使得 a 2 − n a^2-n a2−n 是模 p p p 意义下的二次非剩余。根据定理 1 1 1,找到满足条件的 a a a 的期望步数是 2 2 2。
- 令 ω = a 2 − n \omega=\sqrt{a^2-n} ω=a2−n,解出 x 0 = ( a + ω ) p + 1 2 x_0=(a+\omega)^{\frac{p+1}2} x0=(a+ω)2p+1,那么解有两个: x 0 x_0 x0 和 − x 0 -x_0 −x0。
简单说一下这样做的正确性吧。
定理 3 3 3: ω p ≡ − ω ( m o d p ) \omega^p\equiv-\omega\pmod p ωp≡−ω(modp)。
ω p ≡ ω ω p − 1 ≡ ω ( ω 2 ) p − 1 2 ≡ ω ( a 2 − n ) p − 1 2 ≡ − ω ( m o d p ) \omega^p\equiv \omega\omega^{p-1}\equiv\omega(\omega^2)^{\frac {p-1}2}\equiv\omega(a^2-n)^{\frac {p-1}2}\equiv-\omega\pmod p ωp≡ωωp−1≡ω(ω2)2p−1≡ω(a2−n)2p−1≡−ω(modp)
定理 4 4 4: ( a + b ) p ≡ a p + b p ( m o d p ) (a+b)^p\equiv a^p+b^p\pmod p (a+b)p≡ap+bp(modp)。
用二项式定理展开,发现对于组合数 ( p i ) \binom p i (ip),只有当 i = 0 i=0 i=0 或 i = p i=p i=p 时不含 p p p 这个因子,其他的都被模掉了。
那么对于解 x 0 = ( a + ω ) p + 1 2 x_0=(a+\omega)^{\frac{p+1}2} x0=(a+ω)2p+1,我们可以验证一下:
x 0 2 = ( a + ω ) p + 1 = ( a + ω ) p ( a + ω ) = ( a p + ω p ) ( a + ω ) \begin{aligned} x_0^{\,2}&=(a+\omega)^{p+1}\\ &=(a+\omega)^p(a+\omega)\\ &=(a^p+\omega^p)(a+\omega)\\ \end{aligned} x02=(a+ω)p+1=(a+ω)p(a+ω)=(ap+ωp)(a+ω)
然后由于费马小定律, a p = a ( m o d p ) a^{p}=a\pmod p ap=a(modp),又根据定理 3 3 3,所以:
x 0 2 = ( a − ω ) ( a + ω ) = a 2 − ω 2 = a 2 − ( a 2 − n ) = n \begin{aligned} x_0^{\,2}&=(a-\omega)(a+\omega)\\ &=a^2-\omega^2\\ &=a^2-(a^2-n)\\ &=n \end{aligned} x02=(a−ω)(a+ω)=a2−ω2=a2−(a2−n)=n
所以这是对的。
由于 a 2 − n a^2-n a2−n 是二次非剩余,需要一个类似于记复数的方式记它。
附上洛谷二次剩余模板的代码:
#include<bits/stdc++.h>
using namespace std;
namespace IO{
const int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline T Read(){
char c=gc();T x=0,f=1;
while(!isdigit(c)) f^=(c=='-'),c=gc();
while( isdigit(c)) x=(x+(x<<2)<<1)+(c^48),c=gc();
return f?x:-x;
}
inline int in() {return Read<int>();}
}
using IO::in;
int n,P,I,val;
int add(int x,int y) {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y) {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y) {return 1ll*x*y>=P?1ll*x*y%P:x*y;}
int power(int a,int b){
int ans=1;
for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans,a);
return ans;
}
struct num{
int x,y;
num(int x=0,int y=0):x(x),y(y){}
friend num operator*(const num &a,const num &b){
return num(add(mul(a.x,b.x),mul(val,mul(a.y,b.y))),add(mul(a.x,b.y),mul(a.y,b.x)));
}
friend num operator^(num a,int b){
num ans(1,0);
for(;b;b>>=1,a=a*a) if(b&1) ans=ans*a;
return ans;
}
};
int main(){
int T=in();
while(T--){
n=in(),P=in(),n%=P;
if(!n) {puts("0");continue;}
if(P==2) {printf("%d\n",n);continue;}
if(power(n,(P-1)>>1)!=1) {puts("Hola!");continue;}
while(1){
I=rand()%P,val=dec(mul(I,I),n);
if(power(val,(P-1)>>1)==P-1) break;
}
int x=(num(I,1)^((P+1)>>1)).x;
x=(P-x<x?P-x:x),printf("%d %d\n",x,P-x);
}
return 0;
}