乘法逆元
定义
在同余方程中有(在后文中默认省略同余方程中的 \(mod=p\) 条件)
发现除法无法维护同余性质,考虑维护。
要使最后一个等式成立,则需找到一个 \(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\) 则:
复杂度: \(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\)
则有如下推论:
可以发现这样只要知道 \(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}\),则
复杂度: \(O(n+\log p)\)
[[组合数]]
\(n\) 个不同数中取 \(m\) 个组成的集合方案数
\(n\) 个不同数中取 \(m\) 个组成的排列方案数
求排列组合方法:
- 递推: \(\binom n m=\binom {n-1} {m}+\binom {n-1} {m-1}\)
- \(n,m\) 较小时: 预处理 \(n!,m!,(n-m)!\)
卢卡斯定理
在模 \(p\) 意义下求 \(\binom{n}{m}\%p\):
\(n,m\) 取值范围较小,直接预处理阶乘
\(n,m\) 取值范围较大时预处理不可接受,若 \(p\) 较小且为质数,考虑结合模性质优化:
对于素数 \(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;
}