简介
二次剩余即模意义开根,求解形如 x 2 ≡ n ( m o d p ) x^2\equiv n\pmod p x 2 ≡ n ( mod p ) 的方程,若方程存在非 0 0 0 解,称 n n n 是模 p p p 的二次剩余,欧拉准则可以判断 n n n 是否为模 p p p 的二次剩余,本文只讨论 p p p 为奇素数的情况。
欧拉准则
当 p p p 为奇素数时,根据费马小定理:
n 2 p − 1 2 ≡ 1 ( m o d p ) ( n p − 1 2 − 1 ) ( n p − 1 2 + 1 ) ≡ 0 ( m o d p ) \begin{aligned}
n^{2{\frac{p-1}{2}}}&\equiv 1\pmod p\\
(n^{\frac{p-1}{2}}-1)(n^{\frac{p-1}{2}}+1)&\equiv 0\pmod p\\
\end{aligned}
n 2 2 p − 1 ( n 2 p − 1 − 1 ) ( n 2 p − 1 + 1 ) ≡ 1 ( mod p ) ≡ 0 ( mod p )
为方便表达引入勒让德符号:
( n p ) ≡ n p − 1 2 ( m o d p ) (\frac{n}{p})\equiv n^{\frac{p-1}{2}}\pmod p
( p n ) ≡ n 2 p − 1 ( mod p )
下面证明 n n n 为模 p p p 的二次剩余是 ( n p ) ≡ 1 ( m o d p ) (\frac{n}{p})\equiv 1\pmod p ( p n ) ≡ 1 ( mod p ) 的充要条件。
充分性:若存在 x 2 ≡ n ( m o d p ) x^2\equiv n\pmod p x 2 ≡ n ( mod p ) 那么 n p − 1 2 ≡ x p − 1 ≡ 1 ( m o d p ) n^{\frac{p-1}{2}}\equiv x^{p-1}\equiv 1\pmod p n 2 p − 1 ≡ x p − 1 ≡ 1 ( mod p ) 。
必要性:设 g g g 为模 p p p 的原根,由于 p p p 为奇素数所以一定存在,令 g k ≡ n ( m o d p ) g^k\equiv n\pmod p g k ≡ n ( mod p ) ,那么 g k p − 1 2 ≡ 1 ( m o d p ) g^{k\frac{p-1}{2}}\equiv 1\pmod p g k 2 p − 1 ≡ 1 ( mod p ) ,那么就有 k p − 1 2 ≡ 0 ( m o d φ ( p ) ) k\frac{p-1}{2}\equiv 0\pmod {\varphi(p)} k 2 p − 1 ≡ 0 ( mod φ ( p )) ,也就是说 ( p − 1 ) ∣ k 2 ( p − 1 ) (p-1)\mid \frac{k}{2}(p-1) ( p − 1 ) ∣ 2 k ( p − 1 ) ,所以 k k k 是偶数,那么 x = g k 2 ( m o d p ) x=g^{\frac{k}{2}}\pmod p x = g 2 k ( mod p ) 就是原方程的一个解。
关于解的数量,当 x 1 x_1 x 1 是原方程的一个解时,若存在 x 2 ≢ x 1 ( m o d p ) x_2\not\equiv x_1\pmod p x 2 ≡ x 1 ( mod p ) 并且 x 1 2 ≡ x 2 2 x_1^2\equiv x_2^2 x 1 2 ≡ x 2 2 可以推出:
( x 1 − x 2 ) ( x 1 + x 2 ) ≡ 0 ( m o d p ) (x_1-x_2)(x_1+x_2)\equiv 0\pmod p
( x 1 − x 2 ) ( x 1 + x 2 ) ≡ 0 ( mod p )
所以只有可能是 x 1 ≡ − x 2 ( m o d p ) x_1\equiv -x_2\pmod p x 1 ≡ − x 2 ( mod p ) ,方程有两个解,当 n = 0 n=0 n = 0 时 x 1 ≡ x 2 ≡ 0 ( m o d p ) x_1\equiv x_2\equiv0\pmod p x 1 ≡ x 2 ≡ 0 ( mod p ) 。
Cipolla
首先找到一个 a a a 使得 ( a 2 − n p ) ≡ − 1 ( m o d p ) (\frac{a^2-n}{p})\equiv -1\pmod p ( p a 2 − n ) ≡ − 1 ( mod p ) ,设 g g g 为模 p p p 的原根,且 a 2 − n ≡ g k ( m o d p ) a^2-n\equiv g^k\pmod p a 2 − n ≡ g k ( mod p ) 那么 ( a 2 − n ) p − 1 2 ≡ g k p − 1 2 ( m o d p ) (a^2-n)^{\frac{p-1}{2}}\equiv g^{k\frac{p-1}{2}}\pmod p ( a 2 − n ) 2 p − 1 ≡ g k 2 p − 1 ( mod p ) ,k k k 的奇偶性决定 ( a 2 − n ) p − 1 2 (a^2-n)^{\frac{p-1}{2}} ( a 2 − n ) 2 p − 1 的值,因此平均两次就可以找到满足条件的 a a a 。
类比虚数的定义,令 i 2 ≡ a 2 − n ( m o d p ) i^2\equiv a^2-n\pmod p i 2 ≡ a 2 − n ( mod p ) ,那么有一个很好的性质:
i p ≡ i ⋅ i p − 1 ≡ i ( a 2 − n ) p − 1 2 ≡ − i ( m o d p ) i^p\equiv i\cdot i^{p-1}\equiv i(a^2-n)^{\frac{p-1}{2}}\equiv -i\pmod p
i p ≡ i ⋅ i p − 1 ≡ i ( a 2 − n ) 2 p − 1 ≡ − i ( mod p )
下面证明 ( a + i ) p + 1 ≡ n ( m o d p ) (a+i)^{p+1}\equiv n\pmod p ( a + i ) p + 1 ≡ n ( mod p ) ,其中 ( a + i ) p ≡ ( a p + i p ) ( m o d p ) (a+i)^p\equiv (a^p+i^p)\pmod p ( a + i ) p ≡ ( a p + i p ) ( mod p ) 原理是二项式定理:
( a + i ) p + 1 ≡ ( a + i ) p ( a + i ) ≡ ( a p + i p ) ( a + i ) ≡ ( a − i ) ( a + i ) ≡ a 2 − i 2 ≡ n ( m o d p ) \begin{aligned}
(a+i)^{p+1}&\equiv(a+i)^p(a+i)\\
&\equiv(a^p+i^p)(a+i)\\
&\equiv(a-i)(a+i)\\
&\equiv a^2-i^2\\
&\equiv n\pmod p
\end{aligned}
( a + i ) p + 1 ≡ ( a + i ) p ( a + i ) ≡ ( a p + i p ) ( a + i ) ≡ ( a − i ) ( a + i ) ≡ a 2 − i 2 ≡ n ( mod p )
所以 x = ( a + i ) p + 1 2 x=(a+i)^{\frac{p+1}{2}} x = ( a + i ) 2 p + 1 就是原方程的一个解,下面用反证法证明 x x x 的虚部为 0 0 0 :
设存在 c , d c,d c , d 使得 ( c + d i ) 2 ≡ n ( m o d p ) (c+di)^2\equiv n\pmod p ( c + d i ) 2 ≡ n ( mod p ) 且 d ≠ 0 d\ne 0 d = 0 ,那么有:
c 2 + 2 c d i + d 2 ( a 2 − n ) ≡ n ( m o d p ) c^2+2cdi+d^2(a^2-n)\equiv n\pmod p\\
c 2 + 2 c d i + d 2 ( a 2 − n ) ≡ n ( mod p )
既然 d ≠ 0 d\ne 0 d = 0 一定是 c = 0 c=0 c = 0 否则无法满足等式,此时:
a 2 − n ≡ n d − 2 ( m o d p ) a^2-n\equiv nd^{-2}\pmod p
a 2 − n ≡ n d − 2 ( mod p )
由于 n d − 2 nd^{-2} n d − 2 一定是二次剩余,所以 a 2 − n a^2-n a 2 − n 也是二次剩余,与假设矛盾,所以 x x x 的虚部一定为 0 0 0 。
因此算法的时间复杂度主要在求幂上,用快速幂做到 O ( log p ) O(\log p) O ( log p ) ,题目是 P5491 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <bits/stdc++.h> using namespace std;namespace IO {}using IO::read;using IO::write;#define int long long int T, n, p, i2;struct Complex { int real, imag; Complex operator *(const Complex& c) const { return {(real * c.real + imag * c.imag % p * i2) % p, (real * c.imag + imag * c.real) % p}; } }; mt19937 rng (chrono::steady_clock::now().time_since_epoch().count()) ;int qmi (int a, int k) { int res = 1 ; while (k) { if (k & 1 ) res = res * a % p; a = a * a % p; k >>= 1 ; } return res; } Complex qmi (Complex a, int k) { Complex res = {1 , 0 }; while (k) { if (k & 1 ) res = res * a; a = a * a; k >>= 1 ; } return res; } int cipolla () { if (!n) return 0 ; if (qmi (n, (p-1 ) / 2 ) != 1 ) return -1 ; int a; do { a = uniform_int_distribution<>(1 , p-1 )(rng); i2 = (a * a - n) % p; if (i2 < 0 ) i2 += p; } while (qmi (i2, (p-1 ) / 2 ) == 1 ); return qmi (Complex{a, 1 }, (p+1 ) / 2 ).real; } signed main () { read (T); while (T--) { read (n, p); int x = cipolla (), y = p-x; if (x == 0 ) write (0 ); else if (x == -1 ) write ("Hola!" ); else x > y && (swap (x, y), 0 ), write (x, ' ' ), write (y); } return 0 ; }
扩域
Cipolla 算法用到了扩域的思想,那么这个思想其实可以应用到诸多递推数列上,如果存在通项公式,就能通过扩域的方法求解。
众所周知,斐波那契的通项公式是:
f n = 1 5 ( ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ) f_n=\frac{1}{\sqrt 5}\left(\Big(\frac{1+\sqrt 5}{2}\Big)^n-\Big(\frac{1-\sqrt 5}{2}\Big)^n\right)
f n = 5 1 ( ( 2 1 + 5 ) n − ( 2 1 − 5 ) n )
如果模数 p = 1 0 9 + 7 p=10^9+7 p = 1 0 9 + 7 ,那么 5 5 5 是非二次剩余,但是可以这样定义:
i 2 ≡ 5 ( m o d p ) i^2\equiv 5\pmod p
i 2 ≡ 5 ( mod p )
此时可以改写通项公式了:
f n ≡ i 5 ( ( 1 + i 2 ) n − ( 1 − i 2 ) n ) ( m o d p ) f_n\equiv \frac{i}{5}\left(\Big(\frac{1+i}{2}\Big)^n-\Big(\frac{1-i}{2}\Big)^n\right)\pmod p
f n ≡ 5 i ( ( 2 1 + i ) n − ( 2 1 − i ) n ) ( mod p )
实现了 P1962 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> using namespace std;namespace IO {}using IO::read;using IO::write;#define int long long const int P = 1000000007 , I5 = 400000003 , I2 = 500000004 ;int n;struct Complex { int real, imag; Complex operator *(const Complex& c) const { return {(real * c.real + imag * c.imag * 5 ) % P, (real * c.imag + imag * c.real) % P}; } Complex operator -(const Complex& c) const { int a = (real - c.real) % P, b = (imag - c.imag) % P; if (a < 0 ) a += P; if (b < 0 ) b += P; return {a, b}; } }; Complex qmi (Complex a, int k) { Complex res = {1 , 0 }; while (k) { if (k & 1 ) res = res * a; a = a * a; k >>= 1 ; } return res; } signed main () { read (n); Complex a = {0 , I5}, b = qmi ({I2, I2}, n) - qmi ({I2, P-I2}, n); return write ((a * b).real), 0 ; }