组合数
组合数是从 中取 个数。它的表示方式 和 。
阶乘公式:
递归公式:
递归公式可以看作新来的一个苹果,我要么选它要么不选它。
Lucas 定理:
几种 case
当 时,用递归公式预处理。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2010, mod = 1e9 + 7;
int p[N][N];
void init(){
for (int i = 0; i < N; i ++)
for (int j = 0; j <= i; j ++){
if (!j) p[i][j] = 1;
else p[i][j] = (p[i - 1][j] + p[i - 1][j - 1]) % mod;
}
}
int main(){
int n; cin >> n;
init();
while (n -- ){
int a, b; cin >> a >> b;
cout << p[a][b] << endl;
}
}
当 时,直接预处理 和 的阶乘和逆元阶乘,其公式为:
,其中逆元用快速幂可求。
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
int fact[N], infact[N];
int qpow(int a, int b, int mod){
int res = 1;
while (b){
if (b & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
b >>= 1;
}
return res;
}
int main(){
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++){
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qpow(i, mod - 2, mod) % mod;
}
int n; cin >> n;
while (n -- ){
int a, b; cin >> a >> b;
cout << ((LL)fact[a] * infact[b] % mod) * infact[a - b] % mod << endl;
}
}
当 ,由于 和 过大,在预处理这俩的阶乘就不合适了,而且 也不一定是质数,所以无法用费马小定理和快速幂求逆元 ,应使用 lucas 定理和朴素公式。求组合数公式:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
int qpow(int a, int b, int mod){
int res = 1;
while(b){
if (b & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
b >>= 1;
}
return res;
}
int C(LL a, LL b, int p){
if (b > a ) return 0;
int res = 1;
for (int i = 1, j = a; i <= b; j --, i ++){
res = (LL)res * j % p;
res = (LL)res * qpow(i, p - 2, p) % p;
}
return res;
}
int lucas(LL a, LL b, int p){
if (a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main(){
int n; cin >> n;
while (n -- ){
LL a, b;
int p;
cin >> a >> b >> p;
cout << lucas(a, b, p) << endl;
}
}
当 ,且不再 mod 谁,也就是说要求一个很大的数,应使用高精度来存储。再观察一下此式:
若对分子和分母分解质因子,对质因子处理,最后相乘即可。需要使用线性筛分解质因子。但分解完质因子,如何求其幂,有个公式是这样的(:( 怎么公式折磨多)):
这个最好在纸上画一遍。举个例子,假如求 中 的次数,大致意思是,除以 ,可以对 都去除一次 ,则幂为 ,除以 ,可以对 去除一次 ,则幂为 ,相加为 。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 5010;
int primes[N], cnt;
int sum[N];
bool tou[N];
void get_primes(int a){
for (int i = 2; i <= a; i ++){
if (!tou[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= a / i; j ++){
tou[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int p){
int res = 0;
while (n){
res += n / p;
n /= p;
}
return res;
}
vector<int> mul(vector<int> a, int b){
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i ++){
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t){
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main(){
int a, b; cin >> a >> b;
get_primes(a);
for (int i = 0; i < cnt; i ++){
int p = primes[i];
sum[i] = get(a, p) - get(a - b, p) - get(b, p);
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i ++){
for (int j = 0; j < sum[i]; j ++){
res = mul(res, primes[i]);
}
}
for (int i = res.size() - 1; i >= 0; i --) printf("%d", res[i]);
puts(" ");
return 0;
}
组合数应用
卡特兰数的应用挺多的。其表达形式:
它的意思是说, 是全部可能情况,而 是不合法情况。二者做差就是合法情况。
满足条件的01序列
给定 个 和 个 ,它们将按照某种顺序排成长度为 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 的个数都不少于 的个数的序列有多少个。输出的答案对 取模。

#include<iostream>
#include<algorithm>
typedef long long LL;
using namespace std;
const int mod = 1e9 + 7;
int qpow(int a, int b, int mod){
int res = 1;
while (b){
if (b & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
b >>= 1;
}
return res;
}
int main(){
int n; cin >> n;
int a = 2 * n, b = n;
int res = 1;
for (int i = a; i >= a - b + 1; i -- ) res = (LL)res * i % mod;
for (int i = 1; i <= b; i ++) res = (LL)res * qpow(i, mod - 2, mod) % mod;
res = (LL)res * qpow(b + 1, mod - 2, mod) % mod;
cout << res;
}
总结
组合数还是挺重要的,掌握基础的组合数是如何计算的以及卡特兰数的应用还是必须的。
最后更新于