组合数
意图计算 C a b C^b_a Cab的值,此处列举三种常用方法。
简单组合计数
a , b a,b a,b很小(小于等于3000左右),采用递推的方法,根据公式 C a b = C a − 1 b − 1 + C a − 1 b C^b_a=C^{b-1}_{a-1}+ C^{b}_{a-1} Cab=Ca−1b−1+Ca−1b,用二维数组 C [ i ] [ j ] C[i][j] C[i][j]表示 C i j C^j_i Cij。
#include<iostream>
#define N 2010
using namespace std;
typedef long long ll;
ll C[N][N];
void init(){//预处理打表2000以内的组合数。
for(int i = 0;i<=2000;++i){
for(int j = 0;j<=i;++j){
if(!j) C[i][j] = 1;
else C[i][j] = C[i-1][j]+C[i-1][j-1];
}
}
}
int main(){
int n,a,b;
init();//打表
cin >> n;
while(n--){
cin >> a >> b;
cout << C[a][b]<<"\n";//查询
}
return 0;
}
阶乘逆元求解
a
,
b
≤
100000
a,b\le100000
a,b≤100000时,已经不能采用简单组合计数的方法,时间复杂度过高。这里可以利用公式
C
a
b
=
a
!
(
a
−
b
)
!
×
b
!
C^b_a = \frac{a!}{(a-b)!\times b!}
Cab=(a−b)!×b!a!,将1~n的阶乘以及其逆元计算得出即可。另外,应用这类方法时,通常都会进行取模运算,此处以mod = 1e9+7为例。
代码如下:
#include<iostream>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
ll fact[100010],infact[100010];
ll qpow(ll a,ll p){
ll ans = 1;
while(p){
if(p&1) ans = (ans*a)%mod;
p>>=1;
a*=a;
a%=mod;
}
return ans%mod;
}
int main(){
int n,a,b;
cin >>n;
fact[0] = infact[0] = 1;
for(int i = 1;i<=100000;++i){
fact[i] = fact[i-1]*i%mod;
infact[i] = infact[i-1]*qpow(i,mod-2)%mod;
//逆元i乘以i-1的阶乘逆元,结果是i的阶乘逆元
//也可以用i的阶乘逆元乘以i,反推得到i-1的阶乘逆元
}
while(n--){
cin >> a >> b;
//三项直接相乘可能会long long 溢出
ll ans = (fact[a]*infact[a-b]%mod)*infact[b]%mod;
cout << ans <<"\n";
}
return 0;
}
Lucas定理
1 ≤ a , b ≤ 1 0 18 , 模 数 1 ≤ p ≤ 1 0 5 1\le a,b\le 10^{18},模数1\le p\le10^5 1≤a,b≤1018,模数1≤p≤105时打表已经不可取了,此处应采用卢卡斯定理 C a b ≡ C a % p b % p × C a / p b / p ( m o d p ) C^b_a\equiv C^{b\%p}_{a\%p}\times C^{b/p}_{a/p}(\mod p) Cab≡Ca%pb%p×Ca/pb/p(modp),当a,b都小于p,而p的大小在 1 0 5 10^5 105,可以通过定义递推得到 C a b C^b_a Cab,若有比p大,则继续调用Lucas定理求解。
#include<iostream>
using namespace std;
typedef long long ll;
ll fact[100010],infact[100010];
ll mod;
ll qpow(ll a,ll p){
ll ans = 1;
while(p){
if(p&1) ans = (ans*a)%mod;
p>>=1;
a*=a;
a%=mod;
}
return ans%mod;
}
ll c(int a,int b){//a,b<=mod,规模小
ll res = 1;
for(int i = 1,j=a;i<=b;++i,--j){
res = res * j % mod;
res = res * qpow(i,mod-2) % mod;
}
return res;
}
ll Lucas(ll a,ll b){//卢卡斯定理
if(a<mod&&b<mod) return c(a,b);
return c(a%mod.b%mod)*Lucas(a/mod,b/mod)%mod;
}
int main(){
ll a,b,n;
cin >>n;
while(n--){
cin >> a >> b >> mod;
cout << Lucas(a,b);
}
return 0;
}