概念:
相信到了数学和数论这一块脱离不了组合的存在
组合数运算:
(1)C(n,m)=C(n-1,m)+C(n-1,m-1)
(2)C(n,m)=
求组合数的求法有很多
适用的范围也是不一样的
模板:
杨辉递推法:
for(int i=0;i<=N;i++)
for(int j=0;j<=i;j++)
c[i][j]=((j==0||j==i)?1:(c[i-1][j]+c[i-1][j-1])%q);
适用于数据范围小,使用度密集
这里使用度就是使用每个组合数的频率
q是模数
N是数据范围
复杂度O(n^2)
公式法:
const int N =1e5 + 5;
int mod=1e9+7;
ll qpow(ll a,ll b){
ll ans=1;
a%=mod;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
ll inv(ll a){return qpow(a,mod-2);}
ll jc[N];
ll C(ll n,ll m){return jc[n]*inv(jc[n-m])%mod*inv(jc[m])%mod;}
void solve(){
jc[0]=1;
for(int i=1;i<=N;i++) jc[i]=jc[i-1]*i%mod; //预处理阶乘
cout<<C(4,2);
}
适用于数据范围大,且使用度稀疏
求一次组合数的复杂度为O(logn)
在大数取模除法中我们通常都用逆元代替
(a/b)%mod=a*inv(b)%mod
剩下的就是直接使用公式求即可
线性逆元法:
ll qpow(ll a, ll b) { ll ans = 1; a %= mod; while (b) { if (b & 1)ans = ans * a % mod; a = a * a % mod; b >>= 1; }return ans; }
ll inv(ll a) { return qpow(a, mod - 2); }
vector<ll>jc,iv;
ll C(ll n, ll m){return jc[n] * iv[m] % mod * iv[n - m] % mod;}
void init(int n){
jc.resize(n+1,0);
iv.resize(n+1,0);
jc[0]=jc[1]=1;
iv[0]=iv[1]=1;
for (ll i = 2;i<=n;i++){
jc[i]=jc[i-1]*i%mod;
iv[i]=iv[i-1]*inv(i)%mod;
}
}
适用于数据范围大,且使用度密集
这也是最常用的方法
复杂度为O(nlogn)
实质就是将每个阶乘和逆元预处理出来
然后O(1)使用公式
这里线性逆元可以参考: