组合数概念
C(a,b) =
a
!
b
!
(
a
−
b
)
!
\frac{a!}{b!(a-b)!}
b!(a−b)!a! =
a
∗
(
a
−
1
)
∗
(
a
−
2
)
∗
.
.
.
.
∗
(
a
−
b
+
1
)
b
!
\frac{a * (a-1)*(a-2)*....*(a-b+1)}{b!}
b!a∗(a−1)∗(a−2)∗....∗(a−b+1)
C(a,0) = 1
意思是从 a 个苹果里选 b 个苹果,共有 C(a,b) 种选法。
性质:C(a,b) = C(a-1,b) + C(a-1,b-1)
怎样理解这个性质呐?
先从 a 个苹果里挑出 1 个苹果 ,那么选法就有两种,①包含这 1 个苹果,那么只用从剩下的 a - 1 个中选 b - 1 个,即 C(a-1,b-1) ②不包含这 1 个苹果,那么需要从剩下的 a - 1 个中选 b 个,即 C(a-1,b)。
求组合数Ⅰ
题目: n 组询问,每组给出两个数 a、b,求 C(a,b) mod (109 + 7)
1 ≤ n ≤ 100000
1 ≤ b ≤ a ≤ 2000
分析:
每算一个组合数,最坏需要循环 2000 次,一共有 100000 次询问,总共时间复杂度就是 2000 * 100000 = 2 * 108,这样算是会超时的。
但是,C(a,b)中所有不同的 a 和 b 只有 20002 = 4 * 106 对儿, 我们可以先预处理出所有的C(a,b),每一次查询时间是 O(1),总共时间就是 4 * 106 ,时间复杂度O(n2)。
怎样预处理呢? 根据这个递推式即可,C(a,b) = C(a-1,b) + C(a-1,b-1)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2010,mod = 1e9+7;
int c[N][N];
void init()
{
for(int i=0; i<N; i++)
for(int j=0; j<=i; j++)
if(!j) c[i][j] = 1; // C(a,0) = 1
else c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod;
}
int main()
{
init();
int n;
cin >> n;
while(n--)
{
int a,b;
cin >> a >> b;
cout << c[a][b] <<endl;
}
}
求组合数Ⅱ
题目: n 组询问,每组给出两个数 a、b,求 C(a,b) mod (109 + 7)
1 ≤ n ≤ 100000
1 ≤ b ≤ a ≤ 105
分析:
这道题a、b的数据范围变了,显然,不能再用第一种方法了。
根据定义做,C(a,b) =
a
!
b
!
(
a
−
b
)
!
\frac{a!}{b!(a-b)!}
b!(a−b)!a!
因为 a ! b ! \frac{a!}{b!} b!a! % p ≠ a ! % p b ! % p \frac{a! \% p}{b !\% p} b!%pa!%p,所以我们可以通过快速幂来求 b! 的逆元。(快速幂及逆元讲解)
我们先预处理出 i 的阶乘 fact[i] 和 i 的阶乘的逆元 infact[i] 。那么C(a,b) = fact[a] * infact[b] * infact[a-b]
时间复杂度是(nlogn)。
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 100010,mod = 1e9 + 7;
int fact[N],infact[N];
// fact[i]是 i 的阶乘,infact[i]是 i 阶乘 % mod的逆元
int qmi(int a,int k,int p) //快速幂
{
int res = 1;
while(k)
{
if(k&1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 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] * qmi(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; //这里要及时取mod,2个10^9相乘不会溢出long long,但三个相乘就会溢出了!
}
}
求组合数Ⅲ
题目: n 组询问,每组询问给a、b、p,其中 p 是质数,求每组的 C(a,b) mod p 的值。
1 ≤ n ≤ 20
1 ≤ b ≤ a ≤ 1018
1 ≤ p ≤ 105
分析:
这个数据范围如果还用 nlogn 的算法是超时的,但我们可以用 lucas定理。这个定理用来求大组合数 C(a,b) % p 的值,p 需是质数
Lucas定理:C(a,b) = C(a%p,b%p) * C(a/p,b/p) % p
下面代码求C时用的是C(a,b) = a ∗ ( a − 1 ) ∗ ( a − 2 ) ∗ . . . . ∗ ( a − b + 1 ) b ! \frac{a * (a-1)*(a-2)*....*(a-b+1)}{b!} b!a∗(a−1)∗(a−2)∗....∗(a−b+1) ,你也可以用 a ! b ! ( a − b ) ! \frac{a!}{b!(a-b)!} b!(a−b)!a!。
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
int p;
int qmi(int a,int k) //快速幂求逆元
{
int res = 1;
while(k)
{
if(k&1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int C(int a,int b)
{
int res = 1;
for(int i=1, j=a; i<=b; i++,j--)
{
res = (LL)res * j % p; // 分子
res = (LL)res * qmi(i,p-2) % p; //分母
}
return res;
}
int lucas(LL a,LL b) // lucas定理
{
if(a<p && b<p) return C(a,b);
return (LL)C(a % p,b % p) * lucas(a / p,b / p) % p;
}
int main()
{
int n;
cin>>n;
while(n--)
{
LL a,b;
cin >> a >> b >> p;
cout<< lucas(a,b) << endl;
}
}
求组合数Ⅳ
题目: 输入a、b,求C(a,b),注意结果可能很大,需要使用高精度计算。
1 ≤ b ≤ a ≤ 5000
分析:
1、先筛出 1~N 内所有的质数 (素数筛)
2、将C(a,b) = a ! b ! ( a − b ) ! \frac{a!}{b!(a-b)!} b!(a−b)!a!
进行分解质因数,变为p1a1 * p2a2 * …* pkak,每个 p 出现的次数就是分子出现的次数减去分母出现的次数,即代码里的 sum[i] = get(a,p) - get(b,p) - get(a-b,p)。
n! 中 p 的次数 = n/p + n/p2 + n/p3 +…,直到p的某个次方大于等于 n。 对应下面的代码,n/p 是 n! 中 p 的倍数的个数, n/p2 是 n! 中 p2 的倍数的个数…依次类推,累加在一起就是 p 的次数。
int get(int n,int p) // 求 n! 里的某个质数 p 的个数
{
int res = 0;
while(n)
{
res += n/p;
n /= p;
}
return res;
}
3、再使用高精度乘法计算出p1a1 * p2a2 * …* pkak (高精度)
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 5010;
int primes[N],cnt;
int sum[N];
bool st[N];
void get_primes(int n) //欧拉筛
{
for(int i=2; i<=n; i++)
{
if(!st[i]) primes[cnt++] = i;
for(int j=0; primes[j] <= n/i; j++)
{
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
int get(int n,int p) // 求 n! 里的某个质数 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() || t; i++)
{
if(i<a.size()) t +=a[i]*b;
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++) //枚举每个质数p的次数
{
int p = primes[i];
sum[i] = get(a,p) - get(b,p) - get(a-b,p);
}
vector<int> res;
res.push_back(1); //res初始等于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--) cout<<res[i];
}