二次剩余 学习笔记
学习资料
约定
以下 p p p 均代指 奇素数, F p \mathbb F_p Fp 指 m o d p \bmod p modp 的域。
二次剩余
定义
若 ∃ y ∈ F p ∧ y ≢ 0 ( m o d p ) \exist y\in \mathbb F_p\wedge y\not\equiv 0\pmod p ∃y∈Fp∧y≡0(modp) 使得 x ≡ y 2 ( m o d p ) x\equiv y^2\pmod p x≡y2(modp),则 x x x 是 m o d p \bmod p modp 意义下的二次剩余;不存在这样的 y y y,则 x x x 称为非二次剩余。
性质
一、在 1 , 2 , ⋯ , p − 1 1,2,\cdots,p-1 1,2,⋯,p−1 中恰有 p − 1 2 \dfrac{p-1}2 2p−1 个二次剩余, p − 1 2 \dfrac{p-1}2 2p−1 个非二次剩余。
设 g g g 为 F p \mathbb F_p Fp 的原根,则二次剩余恰为 1 , g 2 , g 4 , ⋯ , g p − 3 1,g^2,g^4,\cdots,g^{p-3} 1,g2,g4,⋯,gp−3
二、 x y xy xy 是二次剩余当且仅当 x , y x,y x,y 均是二次剩余或均不是二次剩余。
三、勒让德符号
(
a
p
)
=
{
0
a
≡
0
(
m
o
d
p
)
1
a
是
二
次
剩
余
−
1
a
不
是
二
次
剩
余
\left(\dfrac a p\right)= \left\{\begin{matrix} 0 & a\equiv 0\pmod p\\ 1 & a 是二次剩余\\ -1 & a不是二次剩余 \end{matrix}\right.
(pa)=⎩⎨⎧01−1a≡0(modp)a是二次剩余a不是二次剩余
有
(
a
p
)
≡
a
(
p
−
1
)
/
2
\left(\dfrac a p\right)\equiv a^{(p-1)/2}
(pa)≡a(p−1)/2. 这就是二次剩余的判断方法。
证:
设 a = g u a=g^u a=gu,则 a ( p − 1 ) / 2 = g u ( p − 1 ) / 2 a^{(p-1)/2}=g^{u(p-1)/2} a(p−1)/2=gu(p−1)/2
当 u u u 是奇数(即 a a a 为非二次剩余)时, g u ( p − 1 ) / 2 ≡ g ( p − 1 ) / 2 ≡ − 1 g^{u(p-1)/2}\equiv g^{(p-1)/2}\equiv -1 gu(p−1)/2≡g(p−1)/2≡−1.
当 u u u 是偶数(即 a a a 为非二次剩余)时, g u ( p − 1 ) / 2 ≡ g 0 ≡ 1 g^{u(p-1)/2}\equiv g^0\equiv 1 gu(p−1)/2≡g0≡1.
Q.E.D.
根据性质二,有
(
a
b
p
)
≡
(
a
p
)
(
b
p
)
\left(\dfrac {ab}p\right)\equiv\left(\dfrac ap\right)\left(\dfrac bp\right)
(pab)≡(pa)(pb)
求二次剩余:Cipolla 算法
如何快速求一个 x x x,使 x 2 ≡ n ( m o d p ) x^2\equiv n\pmod p x2≡n(modp) ( n n n 是 m o d p \bmod p modp 的二次剩余)?
首先我们先找到一个 a a a,使得 a 2 − n a^2-n a2−n 为 非二次剩余。可以证明这样的 a a a 有 p − 1 2 \dfrac{p-1}2 2p−1 个,直接随机判断即可。
然后我们建立“复数域”。定义 i 2 ≡ a 2 − n i^2\equiv a^2-n i2≡a2−n。
则 x 2 ≡ n ( m o d p ) x^2\equiv n\pmod p x2≡n(modp) 的解为 ( a + i ) ( p + 1 ) / 2 (a+i)^{(p+1)/2} (a+i)(p+1)/2.
Cipolla算法的证明
首先有一些定理:
定理1: ( 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)。
二项式定理展开即可。
定理2: i p ≡ i − 1 i^p\equiv i^{-1} ip≡i−1
证:
i
p
≡
i
p
−
1
⋅
i
≡
(
i
2
)
(
p
−
1
)
/
2
⋅
i
≡
(
a
2
−
n
)
(
p
−
1
)
/
2
⋅
i
≡
−
i
Q
.
E
.
D
.
i^p\equiv i^{p-1}\cdot i\\ \equiv (i^2)^{(p-1)/2}\cdot i\\ \equiv (a^2-n)^{(p-1)/2}\cdot i \equiv -i\\ Q.E.D.
ip≡ip−1⋅i≡(i2)(p−1)/2⋅i≡(a2−n)(p−1)/2⋅i≡−iQ.E.D.
有了这两个定理,那么我们可以验证:
x
≡
(
a
+
i
)
(
p
+
1
)
/
2
≡
(
(
a
+
i
)
p
(
a
+
i
)
)
1
/
2
≡
(
(
a
p
+
i
p
)
(
a
+
i
)
)
1
/
2
≡
(
(
a
−
i
)
(
a
+
i
)
)
1
/
2
≡
(
a
2
−
i
2
)
1
/
2
≡
n
1
/
2
x\equiv (a+i)^{(p+1)/2}\\ \equiv ((a+i)^p(a+i))^{1/2}\\ \equiv ((a^p+i^p)(a+i))^{1/2}\\ \equiv ((a-i)(a+i))^{1/2}\\ \equiv (a^2-i^2)^{1/2} \equiv n^{1/2}
x≡(a+i)(p+1)/2≡((a+i)p(a+i))1/2≡((ap+ip)(a+i))1/2≡((a−i)(a+i))1/2≡(a2−i2)1/2≡n1/2
故
x
≡
(
a
+
i
)
(
p
+
1
)
/
2
≡
n
1
/
2
x\equiv (a+i)^{(p+1)/2}\equiv n^{1/2}
x≡(a+i)(p+1)/2≡n1/2。
接下来还要说明 x ∈ F p x\in \mathbb F_p x∈Fp。由于域中的 k k k 次方程至多有 k k k 个解,而 n n n 为二次剩余,所以 x ∈ F p x\in \mathbb F_p x∈Fp。当然还有一解为 − x -x −x。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
ll x = 0, f = 1; char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
return x * f;
}
ll n;
ll P, a, I2;
struct F2 {ll x, y;};
F2 operator * (const F2& A, const F2& B) {return (F2){A.x * B.x % P + I2 * (A.y * B.y % P) % P, (A.x * B.y + A.y * B.x) % P};}
ll qpow(ll a, int n) {ll ret = 1; for(; n; n >>= 1, a = a * a % P) if(n & 1) ret = ret * a % P; return ret;}
F2 qpow(F2 a, int n) {F2 ret = (F2){1, 0}; for(; n; n >>= 1, a = a * a) if(n & 1) ret = ret * a; return ret;}
ll judge(ll a) {return qpow(a, (P-1) / 2);}
void work() {
n = read(); P = read();
ll k = judge(n);
if(k == 0) {
printf("0\n");
return ;
} else if(k == P-1) {
printf("Hola!\n");
return ;
}
while(1) {
a = rand() % P;
I2 = (a * a % P + P - n) % P;
if(judge(I2) == P-1) break;
}
ll ans1 = qpow((F2){a, 1}, (P+1) / 2).x % P;
ll ans2 = P-ans1;
if(ans1 > ans2) swap(ans1, ans2);
printf("%lld %lld\n", ans1, ans2);
}
int main() {
int T = read();
while(T--) work();
return 0;
}