简介
数论是纯粹数学的分支之一,主要研究整数的性质。整数可以是方程式的解。有些解析函数中包括了一些整数、质数的性质,透过这些函数也可以了解一些数论的问题。
数论被高斯成为“数学皇后”,是一大必不可少的分支。
初等数论主要研究的是整除理论和同余理论。下面将会给出本博客介绍的东西:
逆元。
欧几里得和扩展欧几里得。
中国剩余定理和扩展中国剩余定理。
BSGS(大步小步算法)算法。
Lucas 定理。
模意义与逆元
考虑这样一个整数除法 27 ÷ 4 = 6 ⋯ 3 27\div 4 = 6\cdots 3 2 7 ÷ 4 = 6 ⋯ 3 。这样的式子可以用模运算记为 27 m o d 4 = 3 27\bmod 4 = 3 2 7 m o d 4 = 3 或 27 ≡ 3 ( m o d 4 ) 27\equiv 3\pmod 4 2 7 ≡ 3 ( m o d 4 ) 。它们的意义有略微的区别:前者表示 27 27 2 7 除以 4 4 4 的余数是 3 3 3 ,后者是 27 27 2 7 在模 4 4 4 意义下和 3 3 3 是相同的。
对于三个数 a , b , p a,b,p a , b , p ,若 a ÷ p , b ÷ p a\div p,b\div p a ÷ p , b ÷ p 余数相同,就称 a ≡ b ( m o d p ) a\equiv b\pmod p a ≡ b ( m o d p ) 。
模运算有如下性质:
( a + b ) m o d p = ( a m o d p + b m o d p ) m o d p (a+b)\bmod p = (a\bmod p + b\bmod p)\bmod p
( a + b ) m o d p = ( a m o d p + b m o d p ) m o d p
( a − b ) m o d p = ( a m o d p − b m o d p ) m o d p (a-b)\bmod p = (a\bmod p - b\bmod p)\bmod p
( a − b ) m o d p = ( a m o d p − b m o d p ) m o d p
a b m o d p = [ ( a m o d p ) ( b m o d p ) ] m o d p ab\bmod p = [(a\bmod p) (b\bmod p)]\bmod p
a b m o d p = [ ( a m o d p ) ( b m o d p ) ] m o d p
注意 a b m o d p ≠ a m o d p b m o d p m o d p \frac{a}{b}\mod p \neq \frac{a\bmod p}{b\mod p}\bmod p b a m o d p = b m o d p a m o d p m o d p 。例如 24 m o d 13 = 9 , 12 m o d 13 = 12 , 2 m o d 13 = 2 24\bmod 13 = 9, 12\bmod 13 =12 , 2\mod 13 =2 2 4 m o d 1 3 = 9 , 1 2 m o d 1 3 = 1 2 , 2 m o d 1 3 = 2 。
如果我们得到的式子需要用除法该怎么办?不妨把除法转化成乘法。这个时候就相当于,我们需要找到一个数在模意义下的“倒数”,也就是找到一个数 k k k ,满足 a ÷ b ≡ a k ( m o d p ) a\div b\equiv ak\pmod p a ÷ b ≡ a k ( m o d p ) 。这个 k k k 就叫做 b b b 在模 p p p 意义下的逆元。
如何求逆元?当 p p p 是质数是,可以用费马小定理:
a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1\pmod p
a p − 1 ≡ 1 ( m o d p )
于是有 a p − 2 a^{p-2} a p − 2 和 a a a 在模 p p p 意义下互为倒数。因此 a p − 2 a^{p-2} a p − 2 是 a a a 的一个逆元。
若 p p p 不是质数,后面还会提到用欧几里得求逆元。
扩展欧几里得
数论题中经常会要求你求出一个这样的不定方程的整数解(其中参数也是整数):
a x + b y = c ax+by=c
a x + b y = c
这时如果直接暴力枚举显然不现实。因此我们需要一个优秀的算法解决这类问题。
对于不定方程 a x + b y = c ax+by=c a x + b y = c ,x , y x,y x , y 有整数解当且仅当 gcd ( a , b ) ∣ c \gcd(a,b)\mid c g cd( a , b ) ∣ c ,即 gcd ( a , b ) \gcd(a,b) g cd( a , b ) 是 c c c 的约数。
欧几里得算法即辗转相除法,用于计算 gcd ( a , b ) \gcd(a,b) g cd( a , b ) 。需要用到一个公式:
gcd ( a , b ) = gcd ( b , a m o d b ) \gcd(a,b) = \gcd(b,a\bmod b)
g cd( a , b ) = g cd( b , a m o d b )
当 b = 0 b=0 b = 0 时,gcd ( a , b ) = a \gcd(a,b) = a g cd( a , b ) = a 。因此直接递归就可以求解:
1 2 3 4 inline int gcd (int a,int b) { if (!b) return a; return gcd (b,a%b); }
当然也有非递归版本:
1 2 3 4 inline int gcd (int x,int y) { while (y^=x^=y^=x%=y); return x; }
这个看起来比较阴间,其实就是每次 x m o d y x\bmod y x m o d y 后交换 x , y x,y x , y ,直到 y y y 为 0 0 0 。
这时我们已经可以对这个方程有一个无解与否的判断了:如果原方程没有整数解,则 gcd ( a , b ) ∤ c \gcd(a,b)\nmid c g cd( a , b ) ∤ c 。然而我们还没有解这个方程。这就需要用到扩展欧几里得 了。
扩展欧几里得是一个非常强大的算法,本质是边算 gcd ( a , b ) \gcd(a,b) g cd( a , b ) 边求 a x + b y = c ax+by=c a x + b y = c 的解。
扩展欧几里得算法的基本思想,就是在通过辗转相除法求得 gcd ( a , b ) \gcd(a,b) g cd( a , b ) 的同时,通过辗转相处过程中的一些式子,倒回去推导出 x , y x,y x , y 。
假设我们已经找到了一组特殊解 ( x 0 , y 0 ) (x_0,y_0) ( x 0 , y 0 ) 以及 k = gcd ( a , b ) k=\gcd(a,b) k = g cd( a , b ) 。则可以用这一组特解表示所有的解:
{ x = x 0 + b k t y = y 0 − a k t \begin{cases}
x = x_0 + \frac{b}{k}t \\
y = y_0 - \frac{a}{k}t
\end{cases}
{ x = x 0 + k b t y = y 0 − k a t
考虑如何求出一组特解 ( x 0 , y 0 ) (x_0,y_0) ( x 0 , y 0 ) ,考虑辗转相除法的结束状态 a ′ = gcd , b ′ = 0 a'=\gcd,b'=0 a ′ = g cd, b ′ = 0 。此时令 x = 1 x=1 x = 1 有 a ′ + y b ′ = c ′ a'+yb'=c' a ′ + y b ′ = c ′ ,因为 b ′ = 0 b'=0 b ′ = 0 ,所以 y y y 是多少都没关系,当 x = 1 x=1 x = 1 时,c ′ = a ′ = gcd ( a , b ) c'= a' = \gcd(a,b) c ′ = a ′ = g cd( a , b ) ,此时一定有解。
考虑推回去,对于一个状态 ( a , b ) (a,b) ( a , b ) ,我们已经求出了 ( b , a m o d b ) (b,a\bmod b) ( b , a m o d b ) 的解,也就是一组 x 0 , y 0 x_0,y_0 x 0 , y 0 满足 b x 0 + ( a m o d b ) y 0 = gcd bx_0 + (a\bmod b)y_0 = \gcd b x 0 + ( a m o d b ) y 0 = g cd 。
而 a m o d b = a − ⌊ a b ⌋ b a\bmod b = a-\left\lfloor\frac{a}{b}\right\rfloor b a m o d b = a − ⌊ b a ⌋ b :
gcd = b x 0 + ( a − ⌊ a b ⌋ b ) y 0 = b x 0 + a y 0 − ⌊ a b ⌋ b y 0 \begin{aligned}
\gcd &= bx_0 + (a-\left\lfloor\frac{a}{b}\right\rfloor b)y_0 \\
&= bx_0 + ay_0 - \left\lfloor\frac{a}{b}\right\rfloor b y_0
\end{aligned}
g cd = b x 0 + ( a − ⌊ b a ⌋ b ) y 0 = b x 0 + a y 0 − ⌊ b a ⌋ b y 0
对于当前状态 a , b a,b a , b ,要让 a x + b y = gcd ax+by=\gcd a x + b y = g cd ,其实就是:
x ← y 0 x\gets y_0
x ← y 0
y ← x 0 − ⌊ a b ⌋ y 0 y\gets x_0 - \left\lfloor\frac{a}{b}\right\rfloor y_0
y ← x 0 − ⌊ b a ⌋ y 0
于是就得到一个状态怎么用另一个状态得到了,有代码:
1 2 3 4 5 6 7 8 9 10 11 int x1,x2,res;inline int exgcd (int a,int b) { if (!b){ x1=1 ,x2=0 ; return a; } res=exgcd (b,a%b); int x3=x1; x1=x2,x2=x3-x2*(a/b); return res; }
还有一种传址实现:
1 2 3 4 5 6 inline int exgcd (int a,int b,int &x,int &y) { if (!b) return x=1 ,y=0 ,a; int res=exgcd (b,a%b,y,x); y-=a/b*x; return res; }
给定 a , p a,p a , p ,求 a a a 模 p p p 意义下的逆元。
注意到若 x x x 为 a a a 的逆元,有 a x ≡ 1 ( m o d p ) ax\equiv 1\pmod p a x ≡ 1 ( m o d p ) ,于是可以转化成求 a x + p y = 1 ax+py=1 a x + p y = 1 的整数解。就可以用扩欧了。
扩展中国剩余定理
来趁热打铁地了解一下扩展中国剩余定理。不过在此之前还要看一下什么叫做中国剩余定理 (CRT):
孙子定理是中国古代求解一次同余式组(见同余)的方法。是数论中一个重要定理。又称中国余数定理。一元线性同余方程组问题最早可见于中国南北朝时期(公元5世纪)的数学著作《孙子算经》卷下第二十六题,叫做“物不知数”问题。
中国剩余定理需要解决这样一类问题:求解下面这一个一元线性同余方程组的整数解:
{ x ≡ a 1 ( m o d p 1 ) x ≡ a 2 ( m o d p 2 ) ⋮ x ≡ a n ( m o d p n ) \begin{cases}
x \equiv a_1 \pmod{p_1} \\
x \equiv a_2 \pmod{p_2} \\
\quad\vdots \\
x \equiv a_n \pmod{p_n}
\end{cases}
⎩ ⎪ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎪ ⎧ x ≡ a 1 ( m o d p 1 ) x ≡ a 2 ( m o d p 2 ) ⋮ x ≡ a n ( m o d p n )
其中 p 1 , p 2 ⋯ , p n p_1,p_2\cdots,p_n p 1 , p 2 ⋯ , p n 两两互质。
首先需要知道的是,若 a ≡ b ( m o d p ) a\equiv b \pmod p a ≡ b ( m o d p ) ,则 a ÷ k ≡ b ÷ k ( m o d p ) , a + p k ≡ b ( m o d p ) a\div k \equiv b\div k \pmod p,a+pk\equiv b\pmod p a ÷ k ≡ b ÷ k ( m o d p ) , a + p k ≡ b ( m o d p ) 。
下面举一个例子,这是一个小学奥数题:计算一个数,它除以 3 3 3 余 2 2 2 ,除以 5 5 5 余 7 7 7 ,除以 7 7 7 余 2 2 2 。
先给每个方程依次写出一个解 x 1 , x 2 , x 3 x_1,x_2,x_3 x 1 , x 2 , x 3 。考虑从 x 1 x_1 x 1 除法,找一个 x 2 x_2 x 2 使得 ( x 1 + x 2 ) m o d 3 = 2 (x_1+x_2)\bmod 3 =2 ( x 1 + x 2 ) m o d 3 = 2 。根据前面的内容得到满足条件需要 3 ∣ x 2 3\mid x_2 3 ∣ x 2 。同理,再找一个数 x 3 x_3 x 3 满足 ( x 1 + x 2 + x 3 ) m o d 3 = 2 (x_1+x_2+x_3)\bmod 3 =2 ( x 1 + x 2 + x 3 ) m o d 3 = 2 需要 3 ∣ x 3 3\mid x_3 3 ∣ x 3 。同理总共能写出如下条件:
5 ∣ x 1 , 7 ∣ x 1 , 3 ∣ x 2 , 7 ∣ x 2 , 3 ∣ x 3 , 5 ∣ x 3 5\mid x_1, 7\mid x_1,3\mid x_2,7 \mid x_2, 3\mid x_3, 5\mid x_3
5 ∣ x 1 , 7 ∣ x 1 , 3 ∣ x 2 , 7 ∣ x 2 , 3 ∣ x 3 , 5 ∣ x 3
如果找到了符合条件的 x 1 , x 2 , x 3 x_1,x_2,x_3 x 1 , x 2 , x 3 ,把它们加起来就是原方程的一个解。
考虑 x 1 x_1 x 1 有关的条件,相当于在 5 × 7 = 35 5\times7=35 5 × 7 = 3 5 的倍数里找 x 1 x_1 x 1 满足 x 1 m o d 3 = 2 x_1\bmod 3 = 2 x 1 m o d 3 = 2 。为 2 2 2 不好求,考虑求一个余数为 1 1 1 ,然后让这个解乘上 2 2 2 。而求一个 x 1 x_1 x 1 满足 x 1 ≡ 1 ( m o d 3 ) x_1\equiv 1\pmod 3 x 1 ≡ 1 ( m o d 3 ) 的形式和上面的扩展欧几里得求逆元是一样的,简单转化即可。
对于其他情况也用类似方法分析即可。
然而如果 p 1 , p 2 , ⋯ , p n p_1,p_2,\cdots,p_n p 1 , p 2 , ⋯ , p n 不保证两两互质 ,上面的做法就有问题了,不信可以手玩一下。这时候就要用扩展中国剩余定理 (exCRT)。
这时假设单独提出前两个方程:
{ x ≡ a 1 ( m o d p 1 ) x ≡ a 2 ( m o d p 2 ) \begin{cases}
x \equiv a_1 \pmod{p_1} \\
x \equiv a_2 \pmod{p_2}
\end{cases}
{ x ≡ a 1 ( m o d p 1 ) x ≡ a 2 ( m o d p 2 )
可以简单转化为:
{ x = a 1 + c 1 p 1 x = a 2 + c 2 p 2 \begin{cases}
x = a_1 + c_1 p_1 \\
x = a_2 + c_2 p_2
\end{cases}
{ x = a 1 + c 1 p 1 x = a 2 + c 2 p 2
因此 p 1 c 1 − p 2 c 2 = a 2 − a 1 p_1 c_1 -p_2 c_2 = a_2 - a_1 p 1 c 1 − p 2 c 2 = a 2 − a 1 。这是一个不定方程的形式,可以用扩欧求出一组 ( c 1 , c 2 ) (c_1,c_2) ( c 1 , c 2 ) 。将 c 1 c_1 c 1 代入求 x ′ = p 1 c 1 + a 1 x'=p_1 c_1 + a_1 x ′ = p 1 c 1 + a 1 ,这个 x ′ x' x ′ 对于前两个方程成立。为了让它对后面的方程成立,探求其和后面解的关系,把第三个方程联立进来,求解新的方程的解 x x x 。合并前两个方成后联立:
{ x ≡ x ′ ( m o d l c m ( p 1 , p 2 ) ) x ≡ a 3 ( m o d p 3 ) \begin{cases}
x \equiv x' \pmod{\rm{lcm}(p_1,p_2)} \\
x \equiv a_3 \pmod{p_3}
\end{cases}
{ x ≡ x ′ ( m o d l c m ( p 1 , p 2 ) ) x ≡ a 3 ( m o d p 3 )
然后就又求出了新的 x x x 。再接着联立第四个方程……依次类推即可。
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 int x1,x2,res;inline int exgcd (int a,int b) { if (!b){ x1=1 ,x2=0 ; return a; } res=exgcd (b,a%b); int x3=x1; x1=x2,x2=x3-x2*(a/b); return res; } inline int china () { int a1=a[1 ],b1=b[1 ]; for (register int i=2 ;i<=n;++i){ int a2=a[i],b2=b[i],c=b2-b1; exgcd (a1,a2); int mod=a2/res; x1=((x1*c/res)%mod+mod)%mod; int LCM=lcm (a1,a2); b1=((a1*x1+b1)%LCM+LCM)%LCM; a1=LCM; } return b1%a1; }
BSGS
考虑这样一个问题,给定 a , b , p a,b,p a , b , p ,p p p 是质数,求下面方程的最小非负整数解:
a x ≡ b ( m o d p ) a^x\equiv b\pmod p
a x ≡ b ( m o d p )
求法并不复杂,将 x x x 拆分为 i m − j im-j i m − j 的形式,其中 m = ⌈ p ⌉ m=\left\lceil\sqrt p\right\rceil m = ⌈ p ⌉ ,也就是求出:
a i m − j ≡ b ( m o d p ) a^{im-j}\equiv b\pmod p
a i m − j ≡ b ( m o d p )
也就是:
a i m ≡ b a j ( m o d p ) a^{im}\equiv ba^j \pmod p
a i m ≡ b a j ( m o d p )
开一个 Hash,从 0 ∼ m 0\sim m 0 ∼ m 枚举 j j j ,把 b a j m o d p ba^j \bmod p b a j m o d p 丢进去。然后 1 ∼ m 1\sim m 1 ∼ m 枚举 i i i ,检查 Hash 表里面 a i m m o d p a^{im}\bmod p a i m m o d p 是否存在,如果存在则取出其对应的 j j j 。显然取出的第一个解就是原方程的最小非负整数解。
如果每个数的值域很小,快速幂能预处理,一次询问的复杂度就是 p \sqrt p p 的。否则是 p log n \sqrt p\log n p log n 的(要算快速幂)。如果用 STL 实现就是 p log ( n p ) \sqrt p \log (n\sqrt p) p log ( n p ) 的,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 inline int bsgs (int x,int z,int mod) { mp.clear (); x%=mod,z%=mod; int p=sqrt (1.0 *mod)+1 ; if (!z && !x) return 1 ; if (!x && z) return -1 ; for (register int i=0 ;i<=p;++i) mp[z*mi (x,i,mod) %mod] =i; x=mi (x,p,mod); for (register int i=0 ;i<=p;++i){ int a=mi (x,i,mod); if (mp[a]){ int b=mp[a]; if (i*p>=b) return i*p-b; } } return -1 ; }
Lucas 定理
Lucas 定理用中文表述就是卢卡斯定理。一般用于快速求一个在模意义下的组合数。
虽然把它放在最后一个(写这篇文章时的最后一个,如果后面还有别的知识都是本篇文章的更新版本加的了),但是它可能比上面讲的很多知识都简单许多。确切地说,卢卡斯定理就是:
设 p p p 为质数,有(这里为了简化式子就将斜杠当做整除了):
( n m ) ≡ ( n ÷ p m ÷ p ) ( n m o d p m m o d p ) ( m o d p ) \binom{n}{m} \equiv \binom{n\div p}{m\div p}\binom{n\bmod p}{m\bmod p} \pmod p
( m n ) ≡ ( m ÷ p n ÷ p ) ( m m o d p n m o d p ) ( m o d p )
其中有除法的那一个,可以递归实现,另一个直接套组合数公式求即可(很简单吧):
1 2 3 4 5 6 7 8 9 10 inline int C (int n,int m,int p) { if (m>n) return 0 ; return (fac[n]*inv[m]%p*inv[n-m]%p)%p; } inline int lucas (int n,int m,int p) { if (!m) return 1 ; return lucas (n/p,m/p,p)*C (n%p,m%p,p)%p; }