本文主要记录算法竞赛中简单的数论知识.
快速幂 用 p − 1 p-1 p − 1 对 B B B 进行带余除法,并根据模的乘法法则、费马小定理,得
A B ≡ a q ( p − 1 ) + b ≡ ( a q ) p − 1 ⋅ a b ≡ a b ( mod p ) A^B \equiv a^{q(p-1)+b}\equiv \left(a^q\right)^{p-1} \cdot a^b\equiv a^b\:(\text{mod}\:p) A B ≡ a q ( p − 1 ) + b ≡ ( a q ) p − 1 ⋅ a b ≡ a b ( mod p )
考虑将 b b b 写成二进制形式
b = c 0 ⋅ 2 0 + c 1 ⋅ 2 1 + c 2 ⋅ 2 2 + ⋯ + c n ⋅ 2 n = 2 k 0 + 2 k 1 + ⋯ + 2 k m \begin{aligned} b &= c_0 \cdot 2^0 + c_1 \cdot 2^1 + c_2 \cdot 2^2 + \cdots + c_n \cdot 2^n \\ &= 2^{k_0} + 2^{k_1} + \cdots + 2^{k_m} \end{aligned} b = c 0 ⋅ 2 0 + c 1 ⋅ 2 1 + c 2 ⋅ 2 2 + ⋯ + c n ⋅ 2 n = 2 k 0 + 2 k 1 + ⋯ + 2 k m
其中 c i ∈ { 0 , 1 } c_i \in \{0, 1\} c i ∈ { 0 , 1 } ,i = 0 , 1 , 2 , ⋯ , n i = 0, 1, 2, \cdots, n i = 0 , 1 , 2 , ⋯ , n ,此时
a b = a 2 k 0 + 2 k 1 + ⋯ + 2 k m = a 2 k 0 a 2 k 1 ⋯ a 2 k m \begin{aligned} a^b &= a^{2^{k_0} + 2^{k_1} + \cdots + 2^{k_m}} \\ &= a^{2^{k_0}} a^{2^{k_1}} \cdots a^{2^{k_m}} \end{aligned} a b = a 2 k 0 + 2 k 1 + ⋯ + 2 k m = a 2 k 0 a 2 k 1 ⋯ a 2 k m
记 a 2 k i a^{2^{k_i}} a 2 k i 为 bas \text{bas} bas ,那么就可以不断平方
( a 2 k i ) 2 = a 2 k i + 1 (a^{2^{k_i}})^2=a^{2^{k_i+1}} ( a 2 k i ) 2 = a 2 k i + 1
即可得到一系列的 bas \text{bas} bas ,只需不断在二进制数位为 1 1 1 的时候,将 bas \text{bas} bas 乘到结果 res \text{res} res 即可.
cpp using ll = long long ;
ll fpow ( ll x , ll y , ll MOD ) {
x %= MOD; y %= MOD - 1 ;
ll res = 1 , bas = x;
while (y) {
if (y & 1 ) res = res * bas % MOD;
bas = bas * bas % MOD;
y >>= 1 ;
}
return res;
} 单逆(素数模) p p p 为质数,且 a a a 不是 p p p 的倍数,由费马小定理得
a p − 1 ≡ a ⋅ a p − 2 ≡ 1 ( mod p ) a^{p-1} \equiv a \cdot a^{p-2} \equiv 1 \:(\text{mod}\:p) a p − 1 ≡ a ⋅ a p − 2 ≡ 1 ( mod p )
故 a p − 2 a^{p-2} a p − 2 即为a a a 的逆.
cpp using ll = long long ;
ll inv ( ll a , ll MOD ) {
return fpow (a, MOD - 2 , MOD);
} 多逆(素数模) p p p 为质数,使用递推法求多个逆.假设小于 i i i 的整数的逆均以求出 (i > 1 i > 1 i > 1 ),此时欲求 i − 1 i^{-1} i − 1 :
用 i i i 对 p p p 进行带余除法得
p = ⌊ p / i ⌋ ⋅ i + p mod i p = \lfloor p\,/\,i\rfloor \cdot i + p\:\text{mod}\:i p = ⌊ p / i ⌋ ⋅ i + p mod i
即
⌊ p / i ⌋ ⋅ i + p mod i ≡ 0 ( mod p ) \lfloor p\,/\,i\rfloor \cdot i + p\:\text{mod}\:i \equiv 0 \:(\text{mod}\:p) ⌊ p / i ⌋ ⋅ i + p mod i ≡ 0 ( mod p )
由于 p mod i < i < p p \:\text{mod}\: i < i < p p mod i < i < p ,且 p p p 为质数,故 p mod i p \:\text{mod}\: i p mod i 必然可逆.上式两边同时乘以 i − 1 ⋅ ( p mod i ) − 1 i^{-1}\cdot(p\:\text{mod}\:i)^{-1} i − 1 ⋅ ( p mod i ) − 1 得
⌊ p / i ⌋ ⋅ ( p mod i ) − 1 + i − 1 ≡ 0 ( mod p ) \lfloor p\,/\,i\rfloor \cdot (p\:\text{mod}\:i)^{-1} + i^{-1} \equiv 0 \:(\text{mod}\:p) ⌊ p / i ⌋ ⋅ ( p mod i ) − 1 + i − 1 ≡ 0 ( mod p )
移项得
i − 1 ≡ − ⌊ p / i ⌋ ⋅ ( p mod i ) − 1 ≡ ( p − ⌊ p / i ⌋ ) ⋅ ( p mod i ) − 1 ( mod p ) \begin{aligned} i^{-1} &\equiv -\lfloor p\,/\,i\rfloor \cdot (p\:\text{mod}\:i)^{-1} \\ &\equiv (p-\lfloor p\,/\,i\rfloor) \cdot (p\:\text{mod}\:i)^{-1}\:\:(\text{mod}\:p) \end{aligned} i − 1 ≡ − ⌊ p / i ⌋ ⋅ ( p mod i ) − 1 ≡ ( p − ⌊ p / i ⌋) ⋅ ( p mod i ) − 1 ( mod p )
由于 p mod i < i p \:\text{mod}\: i < i p mod i < i ,故 ( p mod i ) − 1 (p\:\text{mod}\:i)^{-1} ( p mod i ) − 1 已求出,上式即为递推式.
初始条件:当 i = 1 i = 1 i = 1 时,i − 1 = 1 i^{-1} = 1 i − 1 = 1 .
cpp using ll = long long ;
ll inv[N];
void get_invs ( ll p ) {
inv[ 1 ] = 1 ;
for (ll i = 2 ; i < N; i ++ ) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
} 时间复杂度为 O ( n ) O(n) O ( n ) .
GCD 值域:gcd ( a , b ) ≤ min ( a , b ) \text{gcd}(a, b) \le \min(a, b) gcd ( a , b ) ≤ min ( a , b ) ;
gcd ( a gcd ( a , b ) , b gcd ( a , b ) ) = 1 \gcd\left(\dfrac{a}{\gcd(a, b)}, \dfrac{b}{\gcd(a, b)}\right) = 1 g cd( g cd( a , b ) a , g cd( a , b ) b ) = 1 ;
更相减损术:当 a > b a > b a > b 时,gcd ( a , b ) = gcd ( a − b , b ) ≤ min ( a − b , b ) \text{gcd}(a, b) = \text{gcd}(a - b, b) \le \min(a - b, b) gcd ( a , b ) = gcd ( a − b , b ) ≤ min ( a − b , b ) ,
当 a ≫ b a \gg b a ≫ b 时,最坏复杂度 O ( n ) O(n) O ( n ) ;
辗转相除法:当 a > b a > b a > b 时,gcd ( a , b ) = gcd ( b , a mod b ) ≤ a mod b \text{gcd}(a, b) = \text{gcd}(b, a \:\text{mod}\: b) \le a \:\text{mod}\: b gcd ( a , b ) = gcd ( b , a mod b ) ≤ a mod b ,
当 a a a ,b b b 是斐波那契数列邻项时,最坏复杂度 O ( n ) O(n) O ( n ) ;
已知公有因子:gcd ( k a , k b ) = k gcd ( a , b ) \gcd(ka, kb) = k\gcd(a, b) g cd( ka , kb ) = k g cd( a , b ) ;
幂的最大公约数:gcd ( a n , b n ) = gcd n ( a , b ) \gcd(a^n, b^n) = \gcd^n(a, b) g cd( a n , b n ) = g cdn ( a , b ) ;
单方素因子缺失:若 p ∣ a p \mid a p ∣ a 且 p ∤ b p \nmid b p ∤ b ,p p p 为素数,则 gcd ( a , b ) = gcd ( a p k , b ) \gcd(a, b) = \gcd\left(\dfrac{a}{p^k}, b\right) g cd( a , b ) = g cd( p k a , b ) ,其中 p ∤ a p k p \nmid \dfrac{a}{p^k} p ∤ p k a ;
Stein算法:当 a > b a > b a > b 时,gcd ( a , b ) = { 2 gcd ( a / 2 , b / 2 ) , a , b are even gcd ( a / 2 , b ) , a is even, b is odd gcd ( a , b / 2 ) , a is odd, b is even gcd ( a − b , b ) , a , b are odd \gcd(a, b) = \begin{cases}2\gcd(a \,/\, 2, b \,/\, 2), &a, b\:\text{are even} \\ \gcd(a \,/\, 2, b), &a \:\text{is even,}\:b\:\text{is odd} \\ \gcd(a, b \,/\, 2), &a\:\text{is odd,}\:b\:\text{is even}\\ \gcd(a - b, b), &a, b\:\text{are odd}\end{cases} g cd( a , b ) = ⎩ ⎨ ⎧ 2 g cd( a / 2 , b / 2 ) , g cd( a / 2 , b ) , g cd( a , b / 2 ) , g cd( a − b , b ) , a , b are even a is even, b is odd a is odd, b is even a , b are odd
最坏复杂度 O ( log n ) O(\log n) O ( log n ) .
EGCD 欲求丢番图方程
a x + b y = gcd ( a , b ) ax+by=\text{gcd}(a, b) a x + b y = gcd ( a , b )
特解,需使用拓展欧几里得算法,模板如下:
cpp using ll = long long ;
// return gcd, particular solution x and y.
ll exgcd ( ll a , ll b , ll & x , ll & y ) {
if (b == 0 ) { x = 1 ; y = 0 ; return a; } // ax = a
ll res = exgcd (b, a % b, y, x); // x <-> y
y -= a / b * x; // wtf
return res;
} 欲求
a X + b Y = c aX+bY=c a X + bY = c
通解,由于方程有解,故 gcd ( a , b ) ∣ c \text{gcd}(a, b)\,|\,c gcd ( a , b ) ∣ c ,两边同时乘以 gcd ( a , b ) / c \text{gcd}(a, b)\,/\,c gcd ( a , b ) / c 得
a ⋅ gcd ( a , b ) c X + b ⋅ gcd ( a , b ) c Y = gcd ( a , b ) a\cdot\frac{\text{gcd}(a, b)}{c}X+b\cdot\frac{\text{gcd}(a, b)}{c}Y=\text{gcd}(a, b) a ⋅ c gcd ( a , b ) X + b ⋅ c gcd ( a , b ) Y = gcd ( a , b )
使用拓展欧几里得算法,得 x 0 = gcd ( a , b ) ⋅ X 0 / c x_0 = \text{gcd}(a, b) \cdot X_0 \,/\, c x 0 = gcd ( a , b ) ⋅ X 0 / c ,y 0 = gcd ( a , b ) ⋅ Y 0 / c y_0 = \text{gcd}(a, b) \cdot Y_0 \,/\, c y 0 = gcd ( a , b ) ⋅ Y 0 / c ,即特解
X 0 = c gcd ( a , b ) x 0 , Y 0 = c gcd ( a , b ) y 0 X_0 = \frac{c}{\text{gcd}(a, b)}x_0, \quad Y_0 = \frac{c}{\text{gcd}(a, b)}y_0 X 0 = gcd ( a , b ) c x 0 , Y 0 = gcd ( a , b ) c y 0
通解
X = X 0 + k ⋅ b gcd ( a , b ) , Y = Y 0 − k ⋅ a gcd ( a , b ) X = X_0 + k \cdot \frac{b}{\text{gcd}(a,b)}, \quad Y = Y_0 - k \cdot \frac{a}{\text{gcd}(a,b)} X = X 0 + k ⋅ gcd ( a , b ) b , Y = Y 0 − k ⋅ gcd ( a , b ) a
其中 k k k 为任意整数.X X X 的最小正整数解为
X min = ( X 0 mod b gcd ( a , b ) + b gcd ( a , b ) ) mod b gcd ( a , b ) X_\text{min} = \left(X_0 \:\text{mod}\:\frac{b}{\text{gcd}(a,b)} + \frac{b}{\text{gcd}(a,b)}\right)\:\text{mod}\:\frac{b}{\text{gcd}(a,b)} X min = ( X 0 mod gcd ( a , b ) b + gcd ( a , b ) b ) mod gcd ( a , b ) b
单逆 gcd ( a , m ) = 1 \text{gcd}(a, m) = 1 gcd ( a , m ) = 1 ,同余方程
a x ≡ 1 ( mod m ) ax \equiv 1 \:(\text{mod}\:m) a x ≡ 1 ( mod m )
即丢番图方程
a x + m y = 1 ax + my = 1 a x + m y = 1
欲求 x x x ,只需使用拓展欧几里得算法求出该丢番图方程的一组特解 x 0 x_0 x 0 与 y 0 y_0 y 0 ,再推出最小整数解即可.
cpp using ll = long long ;
ll inv ( ll a , ll MOD ) {
ll x, y;
exgcd (a, MOD, x, y);
return ((x % MOD) + MOD) % MOD; // b / gcd = b
} 除法化为乘以逆 定义除法的模
( a / b ) mod m = ( a b − 1 ) mod m (a \,/\, b) \:\text{mod}\:m = (ab^{-1}) \:\text{mod}\:m ( a / b ) mod m = ( a b − 1 ) mod m
特别地,若 p p p 为质数,则
( a / b ) mod p = ( a b − 1 ) mod p = ( a b p − 2 ) mod p (a \,/\, b) \:\text{mod}\:p = (ab^{-1}) \:\text{mod}\:p = (ab^{p-2}) \:\text{mod}\:p ( a / b ) mod p = ( a b − 1 ) mod p = ( a b p − 2 ) mod p
排列数之和 A n 1 = n A n 2 = n ( n − 1 ) A n 3 = n ( n − 1 ) ( n − 2 ) ⋮ A n n − 1 = n ( n − 1 ) ( n − 2 ) ⋯ × 2 A n n = n ( n − 1 ) ( n − 2 ) ⋯ × 2 × 1 \begin{aligned} \text{A}_n^1 &= n \\ \text{A}_n^2 &= n(n-1) \\ \text{A}_n^3 &= n(n-1)(n-2) \\ &\:\:\,\vdots \\ \text{A}_n^{n-1} &= n(n-1)(n-2) \cdots \times 2 \\ \text{A}_n^n &= n(n-1)(n-2) \cdots \times 2 \times 1\\ \end{aligned} A n 1 A n 2 A n 3 A n n − 1 A n n = n = n ( n − 1 ) = n ( n − 1 ) ( n − 2 ) ⋮ = n ( n − 1 ) ( n − 2 ) ⋯ × 2 = n ( n − 1 ) ( n − 2 ) ⋯ × 2 × 1
观察可知
∑ k = 1 n A n k = n ( 1 + ∑ k = 1 n − 1 A n k ) \sum\limits_{k=1}^n \text{A}_n^k = n\left(1 + \sum\limits_{k=1}^{n-1}\text{A}_n^k\right) k = 1 ∑ n A n k = n ( 1 + k = 1 ∑ n − 1 A n k )
这实质上是递推式,时间复杂度为 O ( n ) O(n) O ( n ) .
组合数(素数模) 一般使用逆元求组合数
C n k ≡ n ! k ! ( n − k ) ! ≡ n ! ( k ! ) − 1 ( ( n − k ) ! ) − 1 ( mod p ) \text{C}_n^k \equiv \frac{n!}{k!(n-k)!} \equiv n!(k!)^{-1}((n-k)!)^{-1} \:(\text{mod}\:p) C n k ≡ k ! ( n − k )! n ! ≡ n ! ( k ! ) − 1 (( n − k )! ) − 1 ( mod p )
其中阶乘与阶乘的逆均可以 O ( n ) O(n) O ( n ) 的时间预处理.对于阶乘的逆,如果从小到大递推
( i ! ) − 1 ≡ i − 1 ( ( i − 1 ) ! ) − 1 ( mod p ) (i!)^{-1}\equiv i^{-1}((i-1)!)^{-1}\:(\text{mod}\:p) ( i ! ) − 1 ≡ i − 1 (( i − 1 )! ) − 1 ( mod p )
实质上仍求了 n n n 次 逆元,没有达到优化的效果,但从大到小递推
( i ! ) − 1 ≡ ( i + 1 ) ( ( i + 1 ) ! ) − 1 ( mod p ) (i!)^{-1}\equiv(i+1)((i+1)!)^{-1}\:(\text{mod}\:p) ( i ! ) − 1 ≡ ( i + 1 ) (( i + 1 )! ) − 1 ( mod p )
总共只求了一次逆元:( n ! ) − 1 (n!)^{-1} ( n ! ) − 1 .总时间复杂度为 O ( n log p ) O(n \log p) O ( n log p ) .
cpp #define MAXN (2e5 + 5)
#define MOD (1e9 + 7)
using ll = long long ;
ll fpow ( ll x , ll y ) {
x %= MOD; y %= MOD - 1 ;
ll bas = x, res = 1 ;
while (y) {
if (y & 1 ) res = res * bas % MOD;
bas = bas * bas % MOD;
y >>= 1 ;
}
}
ll inv ( ll a ) {
return fpow (a, MOD - 2 );
}
ll fac[MAXN + 1 ], fac_inv[MAXN + 1 ];
void fac_init () {
fac[ 0 ] = 1 ;
for (ll i = 1 ; i <= MAXN; i ++ ) {
fac[i] = fac[i - 1 ] * i % MOD;
}
fac_inv[MAXN] = inv (fac[MAXN]);
for (ll i = MAXN - 1 ; i >= 0 ; i -- ) {
fac_inv[i] = fac_inv[i + 1 ] * (i + 1 ) % MOD;
}
}
ll comb ( ll n , ll k ){
if (k > n || k < 0 ) return 0 ;
return fac[n] * fac_inv[k] % MOD * fac_inv[n - k] % MOD;
} 若 n n n 或 k k k 大于 p p p ,以至于 k ! k! k ! 或 ( n − k ) ! (n-k)! ( n − k )! 产生了因数 p p p ,即 p ∣ k ! p\,|\,k! p ∣ k ! 或 p ∣ ( n − k ) ! p\,|\,(n-k)! p ∣ ( n − k )! ,不能保证其逆元存在,此时便可以使用卢卡斯定理化归
C n k ≡ C n / p k / p ⋅ C n mod p k mod p ( mod p ) \text{C}_n^k\equiv\text{C}_{n/p}^{k/p}\cdot\text{C}_{n\:\text{mod}\:p}^{k\:\text{mod}\:p}\:(\text{mod}\:p) C n k ≡ C n / p k / p ⋅ C n mod p k mod p ( mod p )
其中 C n mod p k mod p \text{C}_{n\:\text{mod}\:p}^{k\:\text{mod}\:p} C n mod p k mod p 可以使用逆元以 O ( 1 ) O(1) O ( 1 ) 的时间求解,对剩下的 C n / p k / p \text{C}_{n/p}^{k/p} C n / p k / p 继续使用卢卡斯定理,递归实现(数据过大没法预处理,直接单解)
cpp ll Lucas ( ll n , ll k ) {
if (k == 0 ) return 1 ; // Don't forget that it's a recursion! And for n, k, k->0 faster!
return comb (n % MOD, k % MOD) * Lucas (n / MOD, k / MOD);
}