1. 程式人生 > 其它 >二次剩餘 學習筆記

二次剩餘 學習筆記

技術標籤:學習筆記

二次剩餘 學習筆記

學習資料

OI-wiki, rqy’s blog

約定

以下 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 yFpy0(modp) 使得 x ≡ y 2 ( m o d p ) x\equiv y^2\pmod p xy2(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,,p1 中恰有 p − 1 2 \dfrac{p-1}2 2p1 個二次剩餘, p − 1 2 \dfrac{p-1}2 2p1 個非二次剩餘。

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,,gp3

二、 x y xy x

y 是二次剩餘當且僅當 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)=011a0(modp)aa
( a p ) ≡ a ( p − 1 ) / 2 \left(\dfrac a p\right)\equiv a^{(p-1)/2}

(pa)a(p1)/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(p1)/2=gu(p1)/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(p1)/2g(p1)/21.

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(p1)/2g01.

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 x2n(modp) n n n   m o d   p \bmod p modp 的二次剩餘)?

首先我們先找到一個 a a a,使得 a 2 − n a^2-n a2n非二次剩餘。可以證明這樣的 a a a p − 1 2 \dfrac{p-1}2 2p1 個,直接隨機判斷即可。

然後我們建立“複數域”。定義 i 2 ≡ a 2 − n i^2\equiv a^2-n i2a2n

x 2 ≡ n ( m o d p ) x^2\equiv n\pmod p x2n(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)pap+bp(modp)

二項式定理展開即可。

定理2: i p ≡ i − 1 i^p\equiv i^{-1} ipi1

證:
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. ipip1i(i2)(p1)/2i(a2n)(p1)/2iiQ.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((ai)(a+i))1/2(a2i2)1/2n1/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)/2n1/2

接下來還要說明 x ∈ F p x\in \mathbb F_p xFp。由於域中的 k k k 次方程至多有 k k k 個解,而 n n n 為二次剩餘,所以 x ∈ F p x\in \mathbb F_p xFp。當然還有一解為 − 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;
}