乘法逆元

定义

在同余方程中有(在后文中默认省略同余方程中的 \(mod=p\) 条件)

\[\begin{cases} (a+b)\equiv(a\mod p)+(b\mod p)\\ (a-b)\equiv(a\mod p)-(b\mod p)\\ (a*b)\equiv(a\mod p)(b\mod p)\\ a\equiv b \Rightarrow a^c\equiv b^c\\ ac\equiv bc \quad(c,p互质) \Rightarrow a\equiv b\\ \frac ab\neq \frac{(a\mod p)}{(b\mod p)} \end{cases} \Biggl( mod=p \Biggr) \]

发现除法无法维护同余性质,考虑维护。
要使最后一个等式成立,则需找到一个 \(x\) 满足 \(ax\equiv1(mod=p)\),这个 \(x\) 即为 \(a\) 在模 \(p\) 意义下的乘法逆元,为方便我们称其为 \(x_a\).

那么 \(\frac ab\equiv ab^{-1}\equiv ax_b\equiv \frac{a\mod p}{b\mod p}\),维护了做除法时的同余性质稳定.

这个 \(x\) 固然无法用除法去求,考虑用乘法或其他同余性质去求。

解逆元

快速幂

由费马小定理得, \(p\) 是质数且 \(gcd(a,p)=1\) 则:

\[a^{p-1}\equiv 1,a^{p}=a\Rightarrow a^{p-2}\equiv x_a \]

复杂度: \(O(\log p)\)

拓展欧几里得法

拓展欧几里得法用以求解形如 \(ax+by\equiv gcd(a,b)\),发现令 \(b=p\)\(a\) 互质时,该式子整理得 \(ax\equiv 1\),则解 \(Exgcd(a,p)\) 所得的 \(x\equiv a^{-1}\)

复杂度: \(O(\log p)\)

线性求逆元

\(k=\lfloor\frac pi\rfloor,j=p\mod i\)

则有如下推论:

\[(p-j)/i=k\Rightarrow p=ki+j\Rightarrow ki+j\equiv 0\Rightarrow (ki+j)(x_ix_j)\equiv kj^{-1}+i^{-1}\equiv 0\Rightarrow x_i\equiv -kj^{-1} \]

可以发现这样只要知道 \(x_1\sim x_p\),就能求出每个 \(x_i\),又 \(j<i\),初始时易证 \(x_1\equiv 1\),无需预处理,直接递推。

复杂度: \(O(n+\log p)\)

(求单个逆元时用该式子递推时,本质上是辗转相除法,复杂度 \(O(\log p)\) )

任意n个数的逆元

前面是 \(1\sim n\) 的逆元,若是 \(n\)\(a_i\in [1,n-1]\) 的逆元,则可以通过逆元的性质,进行前缀积计算,对前缀积求逆元前缀积,再用逆元前缀积求单个逆元,减少 \(O(\log p)\) 的求逆元操作。

具体地,记 \(s_i=\Pi a_i,sv_i=s_i^{ -1}\),则

\[(s_i\cdot sv_i=a_i\cdot s_{i-1}\cdot sv_i)\equiv 1\Rightarrow s_{i-1}\cdot sv_i\equiv a^{-1} \]

复杂度: \(O(n+\log p)\)

[[组合数]]

\(n\) 个不同数中取 \(m\) 个组成的集合方案数

\[\begin{equation} \begin{split} C_n^m &=\binom n m\\ &=\binom n {n-m}\\ &=\binom {n-1} {m}+\binom {n-1} {m-1}\\ &=\frac{n!}{(n-m!)m!}\\ &=\frac{A_n^m}{m!}\\ &=\prod_{i=1}^m{\frac {n-i+1}{i}}\\ &=\frac {n-m+1}{m}\times \prod_{i=1}^{m-1}{\frac {n-i+1}{i}}\\ &=\frac {n-m+1}{m}\binom n {m-1} \end{split} \end{equation}\]

\(n\) 个不同数中取 \(m\) 个组成的排列方案数

\[A_n^m=\frac{n!}{(n-m!)}=m!\binom{n}{m}=\prod_{i=1}^m (n-m+1) \]

求排列组合方法:

  1. 递推: \(\binom n m=\binom {n-1} {m}+\binom {n-1} {m-1}\)
  2. \(n,m\) 较小时: 预处理 \(n!,m!,(n-m)!\)

卢卡斯定理

在模 \(p\) 意义下求 \(\binom{n}{m}\%p\):

  1. \(n,m\) 取值范围较小,直接预处理阶乘

  2. \(n,m\) 取值范围较大时预处理不可接受,若 \(p\) 较小且为质数,考虑结合模性质优化:

对于素数 \(p\),有:

\[\binom{n}{m}\equiv \binom{\lfloor n/p\rfloor}{\lfloor m/p\rfloor}\binom{n\bmod p}{m\bmod p}\pmod p. \]

其中,当 \(n<m\) 时,二项式系数 \(\binom{n}{m}\) 规定为 \(0\)

代码

快速幂加求单逆

typedef long long ll;
ll mi(ll x, ll y,ll p) {
    ll res = 1;
    while (y) {
        if (y & 1)res = res * x % p;
        x = x * x % p,y >>= 1;
    }
    return res%p;
}
ll invr(ll a,ll p){return mi(a,p-2,p);}

拓展欧几里得求逆

typedef long long ll;
ll x,y;
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b)return x=1,y=0,a;
	int d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return d;
}
ll inver(ll a,ll p){return exgcd(a,p,x,y),(x%p+p)%p;}

线性求逆

typedef long long ll;
const int N=1e6+1;
ll inv[N];
void init(int p){inv[0]=0,inv[1]=1;for(int i=2;i<=n;++i)inv[i] = (p - p / i) * inv[p % i] % p;}

任意n个数逆

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e6+1;
ll n,m,x,y,inv[N];
//快速幂 
ll mi(ll x, ll y,ll p) {
    ll res = 1;
    while (y) {
        if (y & 1)res = res * x % p;
        x = x * x % p,y >>= 1;
    }
    return res%p;
}
ll invr(ll a,ll p){return mi(a,p-2,p);}
//拓展gcd 
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b)return x=1,y=0,a;
	int d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return d;
}
ll inver(ll a,ll p){return exgcd(a,p,x,y),(x%p+p)%p;}
/*线性*/
void init(int p){inv[0]=0,inv[1]=1;for(int i=2;i<=n;++i)inv[i] = (p - p / i) * inv[p % i] % p;}
/*任意数*/
ll a[N],s[N]={1},sinv[N],sv[N];
void solve(int n,int p){
	for (int i = 1; i <= n; ++i) s[i] = s[i - 1] * a[i] % p;
	sv[n] = mi(s[n], p - 2,p)/*或exgcd求逆元*/;
	for (int i = n; i >= 1; --i) sv[i - 1] = sv[i] * a[i] % p;
	for (int i = 1; i <= n; ++i) sinv[i] = sv[i] * s[i - 1] % p;	
}
int main(){
	cin>>n>>m,init(m);
	for(int i=1;i<n;++i)if(inv[i]!=inver(i,m)||inv[i]!=mi(i,m-2,m))cout<<"Ops! "<<i<<' '<<inv[i]<<' '<<inver(i,m)<<' '<<mi(i,m-2,m)<<endl;
	return 0;
}

线性求逆加O(1)预处理1~p组合数加卢卡斯(仅取模mod可用)

const int N=1e6+1;typedef long long ll;
ll s[N],is[N];
ll n,x,y,inv[N]={0,1},mod,p;
ll mi(ll x, ll y,ll mod) {
    ll res = 1;
    while (y) {
        if (y & 1)res = res * x % mod;
        x = x * x % mod,y >>= 1;
    }
    return res%mod;
}
void init(ll p,ll mod){
//	for(int i=2;i<m;++i)inv[i] = (mod - mod / i) * inv[mod % i] % mod,s[i]=s[i-1]*i,is[i]=is[i-1]*inv[i];
	s[0]=is[0]=1;
	for(ll i=1;i<=p;++i)s[i]=s[i-1]*i%mod;
	is[p]=(s[p],mod-2,mod);
	for(int i=p-1;i>=1;--i)is[i]=is[i+1]*(i+1)%mod;
}
ll C(int x,int y,int mod){return s[x]*is[y]%mod*is[x-y]%mod;}
ll lucas(ll a, ll b, ll p)
{
    if(a < p && b < p) return C(a, b, p);
    return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
posted @ 2025-08-20 20:54  badn  阅读(5)  评论(0)    收藏  举报