gcd#
辗转相除法: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 mod b ) ,递归直到 b = 0 b = 0 b = 0 。
int gcd ( int a , int b ) {
return ! b ? a : gcd (b, a % b);
} cpp
C++17 起可用 std::gcd(a, b),早期版本可用 __gcd(a, b)。
lcm#
gcd ( a , b ) × lcm ( a , b ) = a × b \gcd(a, b) \times \operatorname{lcm}(a, b) = a \times b g cd( a , b ) × lcm ( a , b ) = a × b ,先求 gcd 再 O ( 1 ) O(1) O ( 1 ) 得 lcm。
多个数的 lcm:两两合并即可,不需要先求整体的 gcd。
int lcm ( int a , int b ) {
return a / gcd (a, b) * b; // 先除后乘,防溢出
} cpp
裴蜀定理#
设 a , b a, b a , b 是不全为零的整数,则存在整数 x , y x, y x , y 使得 a x + b y = gcd ( a , b ) ax + by = \gcd(a, b) a x + b y = g cd( a , b ) 。
证明 :设 s = a x 0 + b y 0 s = ax_0 + by_0 s = a x 0 + b y 0 为 a x + b y ax + by a x + b y 能取到的最小正整数。
gcd ( a , b ) ∣ a \gcd(a, b) \mid a g cd( a , b ) ∣ a 且 gcd ( a , b ) ∣ b ⟹ gcd ( a , b ) ∣ s \gcd(a, b) \mid b \implies \gcd(a, b) \mid s g cd( a , b ) ∣ b ⟹ g cd( a , b ) ∣ s 。
设 a = q s + r ( 0 ≤ r < s ) a = qs + r\;(0 \le r < s) a = q s + r ( 0 ≤ r < s ) ,则 r = a − q s = a − q ( a x 0 + b y 0 ) = a ( 1 − q x 0 ) + b ( − q y 0 ) r = a - qs = a - q(ax_0 + by_0) = a(1 - qx_0) + b(-qy_0) r = a − q s = a − q ( a x 0 + b y 0 ) = a ( 1 − q x 0 ) + b ( − q y 0 ) 。由于 s s s 是最小正整数,r = 0 r = 0 r = 0 ,故 s ∣ a s \mid a s ∣ a 。同理 s ∣ b s \mid b s ∣ b ,所以 s ∣ gcd ( a , b ) s \mid \gcd(a, b) s ∣ g cd( a , b ) 。
因此 s = gcd ( a , b ) s = \gcd(a, b) s = g cd( a , b ) 。
逆定理 :若存在 x , y x, y x , y 使 a x + b y = d ax + by = d a x + b y = d ,且 d d d 是 a , b a, b a , b 的正公因数,则 d = gcd ( a , b ) d = \gcd(a, b) d = g cd( a , b ) 。特别地,a x + b y = 1 ⟺ gcd ( a , b ) = 1 ax + by = 1 \iff \gcd(a, b) = 1 a x + b y = 1 ⟺ g cd( a , b ) = 1 。
推论 :a , b a, b a , b 互素时,a b − a − b ab - a - b ab − a − b 是最大的不能用 a x + b y ax + by a x + b y (x , y x, y x , y 为非负整数)表示的正整数。例如 P3951 直接输出 a b − a − b ab - a - b ab − a − b 。
扩展欧几里得#
用于求 a x + b y = gcd ( a , b ) ax + by = \gcd(a, b) a x + b y = g cd( a , b ) 的一组整数解。
设递归求解 gcd ( b , a m o d b ) \gcd(b, a \bmod b) g cd( b , a mod b ) 时得到 b x 2 + ( a m o d b ) y 2 = gcd bx_2 + (a \bmod b)y_2 = \gcd b x 2 + ( a mod b ) y 2 = g cd ,代入 a m o d b = a − ⌊ a / b ⌋ ⋅ b a \bmod b = a - \lfloor a/b \rfloor \cdot b a mod b = a − ⌊ a / b ⌋ ⋅ b :
b x 2 + ( a − ⌊ a / b ⌋ ⋅ b ) y 2 = a y 2 + b ( x 2 − ⌊ a / b ⌋ y 2 ) bx_2 + (a - \lfloor a/b \rfloor \cdot b)y_2 = ay_2 + b(x_2 - \lfloor a/b \rfloor y_2) b x 2 + ( a − ⌊ a / b ⌋ ⋅ b ) y 2 = a y 2 + b ( x 2 − ⌊ a / b ⌋ y 2 )
对照 a x 1 + b y 1 ax_1 + by_1 a x 1 + b y 1 ,得 x 1 = y 2 , y 1 = x 2 − ⌊ a / b ⌋ y 2 x_1 = y_2,\; y_1 = x_2 - \lfloor a/b \rfloor y_2 x 1 = y 2 , y 1 = x 2 − ⌊ a / b ⌋ y 2 。递归边界 b = 0 b = 0 b = 0 时 x = 1 , y = 0 x = 1, y = 0 x = 1 , y = 0 。
int exgcd ( int a , int b , int & x , int & y ) {
if ( ! b) { x = 1 ; y = 0 ; return a; }
int d = exgcd (b, a % b, x, y);
int t = x;
x = y;
y = t - (a / b) * y;
return d;
} cpp
通解:求出特解 ( x 0 , y 0 ) (x_0, y_0) ( x 0 , y 0 ) 后,x = x 0 + k ⋅ b gcd , y = y 0 − k ⋅ a gcd x = x_0 + k \cdot \frac{b}{\gcd},\; y = y_0 - k \cdot \frac{a}{\gcd} x = x 0 + k ⋅ g c d b , y = y 0 − k ⋅ g c d a 。
快速幂#
计算 x y m o d p x^y \bmod p x y mod p ,O ( log y ) O(\log y) O ( log y ) 。
int qpow ( int x , int y , int p ) {
int ans = 1 ;
while (y) {
if (y & 1 ) ans = 1 ll * ans * x % p;
x = 1 ll * x * x % p;
y >>= 1 ;
}
return ans;
} cpp
注意 x x x 和 p p p 较大时中间乘法用 long long。
a a a 在模 b b b 下的逆元 a − 1 a^{-1} a − 1 满足 a ⋅ a − 1 ≡ 1 ( m o d b ) a \cdot a^{-1} \equiv 1 \pmod b a ⋅ a − 1 ≡ 1 ( mod b ) ,等价于解 a x ≡ 1 ( m o d b ) ax \equiv 1 \pmod b a x ≡ 1 ( mod b ) 。
费马小定理#
b b b 为质数时,a − 1 ≡ a b − 2 ( m o d b ) a^{-1} \equiv a^{b-2} \pmod b a − 1 ≡ a b − 2 ( mod b ) 。直接快速幂即可。
扩展欧几里得#
对任意模数,解 a x + b y = 1 ax + by = 1 a x + b y = 1 ,所得 x x x 即为逆元。适用于 b b b 不一定是质数的情况。
线性递推#
求 1 1 1 到 n n n 所有数模 p p p 的逆元,O ( n ) O(n) O ( n ) :
inv[ 1 ] = 1 ;
for ( int i = 2 ; i <= n; ++ i)
inv[i] = 1 ll * (p - p / i) * inv[p % i] % p; cpp
推导:p = ⌊ p / i ⌋ ⋅ i + ( p m o d i ) p = \lfloor p/i \rfloor \cdot i + (p \bmod i) p = ⌊ p / i ⌋ ⋅ i + ( p mod i ) ,两边模 p p p 整理即得。
任意 n n n 个数的逆元#
求 a 1 , … , a n a_1, \dots, a_n a 1 , … , a n 各自的逆元。先算前缀积 s i s_i s i ,快速幂求 s n − 1 s_n^{-1} s n − 1 ,再倒推:
s[ 0 ] = 1 ;
for ( int i = 1 ; i <= n; ++ i) s[i] = 1 ll * s[i - 1 ] * a[i] % p;
sv[n] = qpow (s[n], p - 2 , p);
for ( int i = n; i >= 1 ; -- i) {
sv[i - 1 ] = 1 ll * sv[i] * a[i] % p;
inv[i] = 1 ll * sv[i] * s[i - 1 ] % p;
} cpp
O ( n + log p ) O(n + \log p) O ( n + log p ) 求出全部逆元。
中国剩余定理#
求解同余方程组:
{ x ≡ r 1 ( m o d m 1 ) x ≡ r 2 ( m o d m 2 ) ⋯ x ≡ r n ( m o d m n ) \begin{cases}
x \equiv r_1 \pmod{m_1} \\
x \equiv r_2 \pmod{m_2} \\
\cdots \\
x \equiv r_n \pmod{m_n}
\end{cases} ⎩ ⎨ ⎧ x ≡ r 1 ( mod m 1 ) x ≡ r 2 ( mod m 2 ) ⋯ x ≡ r n ( mod m n )
其中 m i m_i m i 两两互质。
令 M = ∏ m i M = \prod m_i M = ∏ m i ,c i = M / m i c_i = M / m_i c i = M / m i ,d i d_i d i 为 c i c_i c i 在模 m i m_i m i 下的逆元。则:
x ≡ ∑ i = 1 n r i ⋅ c i ⋅ d i ( m o d M ) x \equiv \sum_{i=1}^n r_i \cdot c_i \cdot d_i \pmod M x ≡ i = 1 ∑ n r i ⋅ c i ⋅ d i ( mod M )
long long crt ( const vector < int > & r , const vector < int > & m ) {
long long M = 1 , ans = 0 ;
for ( int mi : m) M *= mi;
for ( int i = 0 ; i < ( int )r. size (); ++ i) {
long long ci = M / m[i];
int x, y;
exgcd (ci % m[i], m[i], x, y);
long long di = (x % m[i] + m[i]) % m[i];
ans = (ans + r[i] * ci % M * di) % M;
}
return ans;
} cpp
模板题:P1495 曹冲养猪 ↗ 。