组合数
1. 求组合数
根据不同的数据范围,求组合数也可以运用不同的方法。由于这是中学的内容,所以这里就不详细介绍了。
求解的总的式子:
C a b = a ! b ! ( a − b ) ! C_a^b=\frac{a!}{b!(a-b)!} Cab=b!(a−b)!a!
表示从 a a a个物品中选出 b b b个的方案数。
(1) 递推法
使用递推式 C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} Cab=Ca−1b+Ca−1b−1
证明:考虑已经得知了 C a − 1 k , k ∈ [ 0 , b ] C_{a-1}^k,k\in[0,b] Ca−1k,k∈[0,b]的结果,那么当前有 a a a个物品时,第 a a a个物品要么被选,要么不被选中。若被选中,则方案一共有 C a − 1 b − 1 C_{a-1}^{b-1} Ca−1b−1个,若不被选中,则方案有 C a − 1 b C_{a-1}^b Ca−1b个,方案累加,得证。
时间复杂度 O ( n 2 ) O(n^2) O(n2)
void init(){
for (int i=0;i<N;++i){
for (int j=0;j<=i;++j){
if (!j) C[i][j] = 1;
else{
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % M;
}
}
}
}
(2) 通过预处理逆元的方式求组合数
直接求出 a ! , 1 b ! , 1 ( a − b ) ! a!,\frac{1}{b!},\frac{1}{(a-b)!} a!,b!1,(a−b)!1,然后再相乘。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn)
int n;
const int N = 1e5 + 5;
const ll M = 1e9 + 7;
ll fac[N], invf[N];// 阶乘 阶乘的逆元
void init(){
fac[0] = invf[0] = 1LL;
for (int i=1;i<N;++i){
fac[i] = fac[i-1] * i % M;
invf[i] = invf[i-1] * qmi(i, M-2, M) % M;
}
}
int main(void){
scanf("%d", &n);
init();
ll a, b;
for (int i=1;i<=n;++i){
scanf("%lld%lld", &a, &b);
ll ans = fac[a] * invf[b] % M * invf[a - b] % M;
printf("%lld\n", ans);
}
return 0;
}
(3) 分解质因数法求组合数(高精度)
首先,可以把 a ! a! a!写成 a ! = p 1 a 1 × p 2 a 2 × . . . × p k a k a!=p_1^{a_1}\times p_2^{a_2}\times ...\times p_k^{a_k} a!=p1a1×p2a2×...×pkak的形式,那么 C a b C_a^b Cab的答案肯定可以写成 C a b = p 1 b 1 × p 2 b 2 × . . . × p k b k , ∀ i ∈ [ 1 , k ] , b i ≤ a i C_a^b=p_1^{b_1}\times p_2^{b_2}\times ...\times p_k^{b_k},\forall i\in[1,k],b_i\leq a_i Cab=p1b1×p2b2×...×pkbk,∀i∈[1,k],bi≤ai。
因此,设 s i s_i si为 a ! a! a!中包含的质因子 p i p_i pi的个数减去 b ! , ( a − b ) ! b!,(a-b)! b!,(a−b)!中包含的 p i p_i pi的个数,则 C a b = ∏ i = 1 k p i s i C_a^b=\prod_{i=1}^{k} p_i^{s_i} Cab=∏i=1kpisi。
那么, a ! a! a!包含的质因子个数为多少呢?答案是 ⌊ a p ⌋ + ⌊ a p 2 ⌋ + ⌊ a p 3 ⌋ . . . \lfloor\frac{a}{p}\rfloor+\lfloor\frac{a}{p^2}\rfloor+\lfloor\frac{a}{p^3}\rfloor... ⌊pa⌋+⌊p2a⌋+⌊p3a⌋...。
- 代码:
const int N = 5000 + 5;
int p[N], sa[N], cnt;
bool st[N];
void Euler(int n){
for (int i=2;i<=n;++i){
if (!st[i]) p[++cnt] = i;
for (int j=1;p[j]<=n/i;++j){
st[p[j] * i] = true;
if (i % p[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> res; int t = 0;
for (int i=0;i<A.size();++i){
t += A[i]*b;
res.push_back(t % 10), t /= 10;
}
while (t){ res.push_back(t%10), t /= 10;}
while (res.back()==0 && res.size()>1) res.pop_back();
return res;
}
int main(void){
int a, b;
scanf("%d%d", &a, &b);
Euler(a);
for (int i=1;i<=cnt;++i){
int cur = p[i];
sa[i] = get(a, cur) - get(a-b, cur) - get(b, cur);
}
vector<int> res;
res.push_back(1);
for (int i=1;i<=cnt;++i){
for (int j=0;j<sa[i];++j){
res = mul(res, p[i]);
}
}
for (int i=res.size()-1;i>=0;--i){
printf("%d", res[i]);
}
puts("");
return 0;
}
(4) Lucas定理求组合数
-
Lucas定理: C a b ≡ C a m o d p b m o d p × C a / p b / p ( m o d p ) C_a^b \equiv C_{a\mod p}^{b \mod p}\times C_{a/p}^{b/p} (\mod p) Cab≡Camodpbmodp×Ca/pb/p(modp)
-
代码
其中,组合数直接通过公式 C a b = a ! b ! ( a − b ) ! = a × ( a − 1 ) × . . . × ( a − b + 1 ) b ! C_a^b=\frac{a!}{b!(a-b)!}=\frac{a\times (a-1)\times...\times (a-b+1)}{b!} Cab=b!(a−b)!a!=b!a×(a−1)×...×(a−b+1)求得
ll C(ll a, ll b, ll p){
if (b > a) return 0;
ll res = 1;
for (int i=1, j=a;i<=b;++i, --j){
res = res * j % p; // 分子
res = res * qmi(i, p-2, p) % p; // 分母
}
return res;
}
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;
}
(5) 取对数求组合数(可能有浮点误差)
此种方法大概可以计算1000以内的组合数。
先假设 m ≤ n 2 m\leq \frac{n}{2} m≤2n吧,否则因为 C ( n , m ) = C ( n , m − n ) C(n,m)=C(n,m-n) C(n,m)=C(n,m−n),令 m = m − n m=m-n m=m−n。
则 x = ln C ( n , m ) = ln ( n ! m ! ( n − m ) ! ) = ∑ i = 1 n ln i − ∑ i = 1 m ln i − ∑ i = 1 n − m ln i x=\ln C(n,m)=\ln (\frac{n!}{m!(n-m)!})=\sum\limits _{i=1}^n\ln i-\sum\limits _{i=1}^m\ln i-\sum\limits _{i=1}^{n-m}\ln i x=lnC(n,m)=ln(m!(n−m)!n!)=i=1∑nlni−i=1∑mlni−i=1∑n−mlni
这样就可以直接计算前缀和 ln n \ln n lnn,则 C ( n , m ) = e x C(n,m)=e^x C(n,m)=ex。
2. 常用公式
(1) 二项式定理
Cannot read property 'type' of undefined
也可写成Cannot read property 'type' of undefined
其中,Cannot read property 'type' of undefined。
二项式定理的一个常用形式为 ( 1 + x ) n = ∑ k = 0 n C n k x k (1+x)^n=\sum\limits_{k=0}^n C_n^kx^k (1+x)n=k=0∑nCnkxk。
那么很明显, ( 1 − x ) n = ∑ k = 0 n ( − 1 ) k C n k x k (1-x)^n=\sum\limits_{k=0}^n (-1)^kC_n^kx^k (1−x)n=k=0∑n(−1)kCnkxk
(2) 其他的一些小公式
C n 0 + C n 1 + . . . + C n n = 2 n C_n^0+C_n^1+...+C_n^n=2^n Cn0+Cn1+...+Cnn=2n(从 n n n取任意个数,显然二进制取或不取)
C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} Cab=Ca−1b+Ca−1b−1(类似DP思想,对于第 b b b个数,取或不取)
C m + n n = C m 0 C n n + C m 1 C n n − 1 + . . . + C m n C n 0 C_{m+n}^n=C_m^0C_n^n+C_m^1C_n^{n-1}+...+C_m^nC_n^0 Cm+nn=Cm0Cnn+Cm1Cnn−1+...+CmnCn0(从 m m m个数取 n n n个, n n n个取0个开始遍历)
C n m = C n n − m C_n^m=C_n^{n-m} Cnm=Cnn−m(显然从 n n n中取 m m m个的方案数和从 n n n中取 n − m n-m n−m个的方案数是一样的)
(这些递推式还是挺容易证明的就不写了,敲公式好累)
(2) 乘法原理和加法原理:类类独立,步步相关
-
加法原理:做一件事情,完成它可以有 n n n类办法,在第一类办法中有 m 1 m_1 m1种不同的方法,在第二类办法中有 m 2 m_2 m2种不同的方法,……,在第 n n n类办法中有 m n m_n mn种不同的方法。那么完成这件事共有 N = ∑ i = 1 n m i N=\sum_{i=1}^n m_i N=∑i=1nmi种不同的方法。
-
乘法原理:做一件事情,完成它需要分成n个步骤,做第一步有 m 1 m_1 m1种不同的方法,做第二步有m2种不同的方法,……,做第 n n n步有种 m n m_n mn不同的方法,那么完成这件事有 N = ∏ i = 1 n m i N=\prod_{i=1}^n m_i N=∏i=1nmi种不同的方法。
(3) 可重复元素的组合问题
从n个元素中,可重复的挑选m个元素组成集合,求:不同的集合有多少个?
答案: C n + m − 1 m C_{n+m-1}^m Cn+m−1m
证明:隔板法。
∣∗∣∗∗∗∗∣∣∗∗∗∣∣∗∣(|是边界,*是球)
假设这里有n个盒子,m个球,如果有k个球被放在了第i个盒子里,那么就相当于第i个数被选了k次。那么去掉两端边界,就相当于摆放n-1个盒子的边界和m个球有多少种摆放方式。从里面选择r个位置放球 剩余的都是盒子边界,因此答案就是 C n + m − 1 m C_{n+m-1}^m Cn+m−1m。
这里有位老哥用dp在 O ( n 2 ) O(n^2) O(n2)搞定了,俺觉得也可以一看wwww,部分dp表真是一个好东西啊。
https://blog.csdn.net/m0_37602827/article/details/100624871
3. 常用定理
(1) Lucas定理
结论: C a b ≡ C a m o d p b m o d p × C a / p b / p ( m o d p ) C_a^b \equiv C_{a\mod p}^{b \mod p}\times C_{a/p}^{b/p} (\mod p) Cab≡Camodpbmodp×Ca/pb/p(modp) ( p p p为素数)
它还可以写成如下形式:
若 p p p为素数,则 n n n的 p p p进制表示为 ( a k , a k − 1 , . . . , a 0 ) (a_k,a_{k-1},...,a_0) (ak,ak−1,...,a0), m m m的 p p p进制表示为 ( b k , b k − 1 , . . . , b 0 ) (b_k,b_{k-1},...,b_0) (bk,bk−1,...,b0)
则 C n m ≡ C a k b k ⋅ C a k − 1 b k − 1 . . . C a 0 b 0 ( m o d p ) C_n^m\equiv C_{a_k}^{b_k}·C_{a_{k-1}}^{b_{k-1}}...C_{a_0}^{b_0} (\mod p) Cnm≡Cakbk⋅Cak−1bk−1...Ca0b0(modp)
很容易看出来,它就是形式一运用数学归纳法可以得出的结论。
证明:
引理1 C ( p , x ) ≡ 0 ( m o d p ) , 0 < x < p C(p,x) \equiv 0(\mod p), 0<x<p C(p,x)≡0(modp),0<x<p
证明:$C(p,x)\equiv \frac{p!}{x!(p-x)!}\equiv \frac{p\times (p-1)!}{x\times(x-1)\times(p-x)!} $
由于 p p p为素数,所以 ∀ x ∈ ( 0 , p ) , ( p , x ) = 1 \forall x\in(0,p),(p,x)=1 ∀x∈(0,p),(p,x)=1,故 x ∣ C p − 1 x − 1 x|C_{p-1}^{x-1} x∣Cp−1x−1,则 p ∣ ( p x C p − 1 x − 1 ) p|(\frac{p}{x}C_{p-1}^{x-1}) p∣(xpCp−1x−1)。
所以 C ( p , x ) ≡ p × ( x − 1 m o d p ) × C ( p − 1 , x − 1 ) ≡ 0 m o d p C(p,x) \equiv p\times(x^{-1} \mod p)\times C(p-1,x-1)\equiv 0\mod p C(p,x)≡p×(x−1modp)×C(p−1,x−1)≡0modp
引理2 ( 1 + x ) p ≡ ( 1 + x p ) m o d p (1+x)^p \equiv (1+x^p) \mod p (1+x)p≡(1+xp)modp
根据二项式定理可得 ( 1 + x ) p = ∑ k = 0 p C p k x k (1+x)^p=\sum\limits _{k=0}^pC_p^kx^k (1+x)p=k=0∑pCpkxk
由引理1可得 ∑ k = 0 p C p k x k ≡ C ( p , 0 ) x 0 + C ( p , p ) x p ≡ ( 1 + x p ) m o d p \sum\limits _{k=0}^pC_p^kx^k\equiv C(p,0)x^0+C(p,p)x^p\equiv (1+x^p) \mod p k=0∑pCpkxk≡C(p,0)x0+C(p,p)xp≡(1+xp)modp
下面正式推导Lucas定理。
假设 n = q 1 p + r 1 , m = q 2 p + r 2 n=q_1p+r_1,m=q_2p+r_2 n=q1p+r1,m=q2p+r2,则 q 1 = n / p , q 2 = m / p q_1=n/p,q_2=m/p q1=n/p,q2=m/p
( 1 + x ) n ≡ ( 1 + x ) q 1 p + r 1 ≡ ( 1 + x ) q 1 p × ( 1 + x ) r 1 ≡ [ ( 1 + x ) p ] q 1 × ( 1 + x ) r 1 ( 1 + x p ) q 1 × ( 1 + x ) r 1 ≡ ∑ i = 0 q 1 C ( q 1 , i ) x p × i × ∑ j = 0 r 1 C ( r 1 , j ) x j ( m o d p ) (1+x)^n\equiv (1+x)^{q_1p+r_1}\equiv (1+x)^{q_1p}\times(1+x)^{r_1}\equiv [(1+x)^p]^{q_1}\times(1+x)^{r_1} \\ (1+x^p)^{q_1}\times(1+x)^{r_1} \equiv \sum\limits _{i=0}^{q_1}C(q_1,i)x^{p\times i}\times \sum\limits _{j=0}^{r_1}C(r_1,j)x^{j} (\mod p) (1+x)n≡(1+x)q1p+r1≡(1+x)q1p×(1+x)r1≡[(1+x)p]q1×(1+x)r1(1+xp)q1×(1+x)r1≡i=0∑q1C(q1,i)xp×i×j=0∑r1C(r1,j)xj(modp)
又 ( 1 + x ) n = ∑ i = 0 n C ( n , i ) x i (1+x)^n=\sum\limits _{i=0}^{n}C(n,i)x^{i} (1+x)n=i=0∑nC(n,i)xi
∴ ∑ i = 0 n C ( n , i ) x i ≡ ∑ i = 0 q 1 C ( q 1 , i ) x p × i × ∑ j = 0 r 1 C ( r 1 , j ) x j ( m o d p ) \therefore \sum\limits _{i=0}^{n}C(n,i)x^{i}\equiv \sum\limits _{i=0}^{q_1}C(q_1,i)x^{p\times i}\times \sum\limits _{j=0}^{r_1}C(r_1,j)x^{j} (\mod p) ∴i=0∑nC(n,i)xi≡i=0∑q1C(q1,i)xp×i×j=0∑r1C(r1,j)xj(modp)
由二项式定理可知, C n m C_n^m Cnm为 ( 1 + x ) n (1+x)^n (1+x)n展开式中 x m x^m xm项前面的系数
∵ m = q 2 p + r 2 , ∴ q 2 = m / p , r 2 = m − ⌊ m p ⌋ × p \because m=q_2p+r_2,\therefore q_2=m/p,r_2=m-\lfloor \frac{m}{p} \rfloor \times p ∵m=q2p+r2,∴q2=m/p,r2=m−⌊pm⌋×p
C ( n , m ) x m ≡ C ( q 1 , q 2 ) x p × q 2 × C ( r 1 , r 2 ) x r 2 ( m o d p ) C(n,m)x^{m}\equiv C(q_1,q_2)x^{p\times q_2}\times C(r_1,r_2)x^{r_2} (\mod p) C(n,m)xm≡C(q1,q2)xp×q2×C(r1,r2)xr2(modp)
即 C n m ≡ C q 1 q 2 ⋅ C r 1 r 2 ( m o d p ) C_n^m\equiv C_{q_1}^{q_2}·C_{r_1}^{r_2} (\mod p) Cnm≡Cq1q2⋅Cr1r2(modp),得证。
推论1 C ( n , m ) C(n,m) C(n,m)为奇数的充要条件为,在二进制表示下( p = 2 p=2 p=2), ∀ i ∈ [ 0 , k ] , a i ≥ b k \forall i\in[0,k],a_i\geq b_k ∀i∈[0,k],ai≥bk。
证明:首先, C ( 0 , 0 ) = 1 , C ( 0 , 1 ) = 0 , C ( 1 , 0 ) = 1 , C ( 1 , 1 ) = 1 C(0,0)=1,C(0,1)=0,C(1,0)=1,C(1,1)=1 C(0,0)=1,C(0,1)=0,C(1,0)=1,C(1,1)=1。
考虑 C ( n , m ) m o d 2 C(n,m)\mod 2 C(n,m)mod2,则对于 n n n上的为1的位 a i a_i ai, b i b_i bi能取0或1,对于 a i = 0 a_i=0 ai=0,则 b i b_i bi必须为0,得证。
例题:HDU4349 Xiao Ming’s Hope 结论就是 2 a i = 1 的 个 数 2^{a_i=1的个数} 2ai=1的个数
(2) 扩展Lucas定理
前置知识:卢卡斯定理 中国剩余定理
扩展Lucas定理用于求解以下公式:
C a b m o d p C_a^b \mod p Cabmodp,其中 p p p不一定是质数。
由整数的唯一分解定理,对 p p p进行质因数分解, p = p 1 a 1 × p 2 a 2 × . . × p k a k p=p_1^{a_1}\times p_2^{a_2}\times .. \times p_k^{a_k} p=p1a1×p2a2×..×pkak,显然 ∀ i , j ∈ [ 1 , k ] , i ≠ j , ( p i a i , p j a j ) = 1 \forall i,j \in [1,k], i\neq j, (p_i^{a_i},p_j^{a_j})=1 ∀i,j∈[1,k],i=j,(piai,pjaj)=1。
我会在后面的提高篇中证明