// 扩展欧几里得:求 ax + by = gcd(a,b) 的一组整数解 ll exgcd( ll a, ll b, ll &x, ll &y ){ if( b == 0 ) { x = 1, y = 0; return a; } ll d = exgcd( b, a % b, y, x ); y -= a / b * x; return d; }
该写法在递归调用中交换 x, y 的位置,使返回后的 x 已经对应推导中的 y′,再修正 y 即可。
若存在整数 x 满足 ax≡1(modm),则称 x 是 a 模 m 的乘法逆元,记作 a−1(modm)。
逆元是剩余类乘法中的逆元素,不是普通实数意义下的 1/a。
这是线性同余方程在 c=1 时的特殊情形,因此逆元存在当且仅当 gcd(a,m)=1。
当逆元存在时,它在模 m 意义下唯一。
模意义下的除法:在 b 可逆时,ba(modm) 应理解为 a⋅b−1(modm)。
5.2 扩展欧几里得求逆元
要求 a−1(modm),等价于求 ax+my=1
只要 gcd(a,m)=1,扩展欧几里得就能给出一组系数 x,y,其中 x 就是 a 的一个逆元。
▶
Exgcd 求逆元
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Exgcd 求逆元 ll exgcd( ll a, ll b, ll &x, ll &y ){ if( b == 0 ) { x = 1, y = 0; return a; } ll d = exgcd( b, a % b, y, x ); y -= a / b * x; return d; }
ll exgcd_inv( ll a, ll mod ){ ll x, y; ll d = exgcd( a, mod, x, y ); if( d != 1 ) return-1; return ( x % mod + mod ) % mod; }
它只要求 gcd(a,m)=1,模数不必是质数。
5.3 费马小定理求逆元
若 p 是质数且 p∤a,费马小定理给出 ap−1≡1(modp),两边乘以 a−1 得到 a−1≡ap−2(modp)。
因此可以使用快速幂求逆元。
▶
费马小定理求逆元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 费马小定理求逆元 ll mul( ll a, ll b, ll mod ){ return (ll)( (i128)a * b % mod ); }
ll qpow( ll a, ll k, ll mod ){ ll res = 1 % mod; a %= mod; for( ; k; k >>= 1, a = mul( a, a, mod ) ) if( k & 1 ) res = mul( res, a, mod ); return res; }
ll fermat_inv( ll a, ll mod ){ returnqpow( a, mod - 2, mod ); }
这一方法要求模数为质数,并且 a 不是模数的倍数。
5.4 线性递推求连续整数的逆元
当模数 p 为质数且 1≤n<p 时,可以在线性时间内求出 1,2,…,n 的全部逆元。
设 p=⌊ip⌋i+(pmodi).
在模 p 意义下整理,可以得到下列递推式:
inv[i]=(p−⌊ip⌋)inv[pmodi]modp.
▶
线性递推求逆元
1 2 3 4 5 6 7
// 线性递推求逆元 ll inv[maxn];
voidinit_linear( int N, ll mod ){ inv[1] = 1; for( int i = 2; i <= N; ++ i ) inv[i] = ( mod - mod / i ) * inv[mod % i] % mod; }
条件 N<p 保证区间内每个整数都可逆。
5.5 一个数组的全部逆元
给定 a1,a2,…,an,若每个 ai 在模 m 下都可逆,可以只求一次总乘积的逆元,再在线性时间内得到每个元素的逆元。
// 数组整体求逆:默认 mod 为质数 ll inv[maxn], pre[maxn], suf[maxn];
ll mul( ll a, ll b, ll mod ){ return (ll)( (i128)a * b % mod ); }
ll qpow( ll a, ll k, ll mod ){ ll res = 1 % mod; a %= mod; for( ; k; k >>= 1, a = mul( a, a, mod ) ) if( k & 1 ) res = mul( res, a, mod ); return res; }
ll fermat_inv( ll a, ll mod ){ returnqpow( a, mod - 2, mod ); }
voidinit_array( int N, ll val[], ll mod ){ pre[0] = 1; for( int i = 1; i <= N; ++ i ) pre[i] = mul( pre[i - 1], val[i], mod ); suf[N] = fermat_inv( pre[N], mod ); for( int i = N; i >= 1; -- i ) { inv[i] = mul( pre[i - 1], suf[i], mod ); if( i > 1 ) suf[i - 1] = mul( suf[i], val[i], mod ); } }
ll mul( ll a, ll b, ll mod ){ return (ll)( (i128)a * b % mod ); }
ll exgcd( ll a, ll b, ll &x, ll &y ){ if( b == 0 ) { x = 1, y = 0; return a; } ll d = exgcd( b, a % b, y, x ); y -= a / b * x; return d; }
ll crt( int N ){ ll M = 1, res = 0; for( int i = 1; i <= N; ++ i ) M *= m[i]; for( int i = 1; i <= N; ++ i ) { ll Mi = M / m[i], ti, y; exgcd( Mi, m[i], ti, y ); ti = ( ti % m[i] + m[i] ) % m[i]; ll term = mul( a[i], Mi, M ); term = mul( term, ti, M ); res = ( res + term ) % M; } return ( res + M ) % M; }
ll mul( ll a, ll b, ll mod ){ return (ll)( (i128)a * b % mod ); }
ll exgcd( ll a, ll b, ll &x, ll &y ){ if( b == 0 ) { x = 1, y = 0; return a; } ll d = exgcd( b, a % b, y, x ); y -= a / b * x; return d; }
ll excrt( int N ){ ll M = m[1], res = a[1]; for( int i = 2; i <= N; ++ i ) { ll x, y, A = M, B = m[i], C = ( a[i] - res % B + B ) % B; ll d = exgcd( A, B, x, y ); if( C % d != 0 ) return-1; x = mul( x, C / d, B / d ); res += x * M, M *= B / d; res = ( res % M + M ) % M; } return res; }