> 文章列表 > 【学习笔记】组合计数

【学习笔记】组合计数

【学习笔记】组合计数

排列和组合

排列数(阶乘)

引入

n n n 个人排成一排,求方案

不妨这样思考:
第一个人先站队,这时他有 n n n 个位置可以选,也就是有 n n n 种方案
第二个人再站队,这时因为第一个人已经进去了,所以他有 n − 1 n-1 n1 种站法
第三个人站队,同理有 n − 2 n-2 n2 种站法
… \\dots
以此类推, i i i 个人有 n − i + 1 n-i+1 ni+1站法

那么根据乘法原理,总共的方案数就有
n ∗ ( n − 1 ) ∗ ( n − 2 ) ∗ ⋯ ∗ 2 ∗ 1 n * (n-1) * (n-2) * \\dots * 2 * 1 n(n1)(n2)21
也就是
n ! n! n!

组合数(C)

引入

n n n 个球里面选出 m m m 个球,有多少种选法

可以这样想,把这 n n n小球排成一排,每次选前 m m m 个小球,那么根据排列数,有 n ! n! n! 种方案

但是我们不考虑小球的顺序,所以和前 m m m 个小球的排列方式无关,和后面的 n − m n-m nm 个小球的顺序也没有关系

所以我们的方案数就是
C n m = n ! m ! ( n − m ) ! C^{m}_{n} = \\frac{n!}{m!(n-m)!} Cnm=m!(nm)!n!
其中 C n m C^m_n Cnm 表示从 n n n 个球里面取 m m m 个球的方案数(当然不限于取小球)

组合数递推公式

C n m = C n − 1 m − 1 + C n − 1 m C^m_n = C^{m-1}_{n-1} + C^m_{n-1} Cnm=Cn1m1+Cn1m

对于最后一个小球,有两种情况:取或者不取 那就分成了两个子问题
1.取最后一个小球,那问题就变成了在 n − 1 n-1 n1 个小球种取 m − 1 m-1 m1 个小球的方案数
2.不取最后一个小球,问题变为在 n − 1 n-1 n1 个小球中取 m m m 个小球的方案数

例题

题目传送门

题目大意

求有多少对i和j满足

  • 0 ≤ i ≤ n 0\\leq i\\leq n 0in
  • 0 ≤ j ≤ m i n ( i , m ) 0\\leq j\\leq min(i,m) 0jmin(i,m)
  • 并且 k k k C ( i , j ) C(i,j) C(i,j) 的约数

开始读入 k , t k,t k,t ,接下来 t t t 组数据,每组读入 n , m n,m n,m

分析

用组合数递推公式可以预处理出所有的方案,然后再直接求就行

复杂度爆了怎么办?用前缀和

Code

代码很丑陋,所以建议去题解区看大佬的代码

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
const int N = 2e3+10;
const int M = 1e4+10;
const int INF = 0x3f3f3f3f;using namespace std;
int c[N][N],cnt[N][N];
int t, k;int main(){cin >> t >> k;	for(int i = 1; i <= 2000;i++) c[i][i] = c[i][0] = 1; for(int i = 1; i <= 2000; i++){for(int j = 1; j <= i; j++){if(j != i) 	c[i][j] = (c[i-1][j-1] + c[i-1][j]) % k;//j == i这里前面处理过了cnt[i][j]  = cnt[i-1][j] + cnt[i][j-1] - cnt[i-1][j-1] + !(c[i][j]);//前缀和}cnt[i][i+1]=cnt[i][i];//方便上面调用 这一步很重要,蒟蒻因为这一步调了半小时不知道问题再哪里}while(t--){int n, m;cin >> n >> m;if(m > n){cout << cnt[n][n] << endl;}else{cout << cnt[n][m] << endl;}}return 0;
}

可重排列和集合

可重排列

引入

n n n 种颜色的球,第 i i i 种颜色的球有 a i a_i ai 个,请问有多少种排列方法

注意这里是要求所有球都要用上

比如说有两种颜色的球,第 1 种有 2 个,第二种有 1 个,那么就有[1 1 2] [1 2 1] [2 1 1] 也就是 3 种方案

你看到了不该看的 \\textcolor{white}{你看到了不该看的} 你看到了不该看的
我们令 S = ∑ i = 1 n a i S = \\sum_{i = 1} ^ n a_i S=i=1nai
把每种颜色的球一个一个的放进去,也就是说

  • 第一步:放第 1 种颜色的球的时候,我们有 C S a 1 \\large C_S^{a_1} CSa1 种放法
  • 第二步:放颜色 2,因为之前已经填好了 a 1 a_1 a1 个数了,还剩 S − a 1 S - a_1 Sa1 个空位,所以方案数为 C S − a 1 a 2 \\large C_{S-a_1}^{a_2} CSa1a2
  • 第三步:放颜色 3,之前已经填好了 a 1 + a 2 a_1 + a_2 a1+a2 个数了,还剩 S − a 1 − a 2 S - a_1 - a_2 Sa1a2 个空位,方案数为 C ( S − a 1 − a 2 ) a 3 \\large C_{(S-a_1-a_2)}^{a_3} C(Sa1a2)a3

这样就可以推出答案为:

C S a 1 ⋅ C S − a 1 a 2 ⋅ C S − a 1 − a 2 a 3 ⋯ = S ! a 1 ! ⋅ ( S − a 1 ) ! × ( S − a 1 ) ! a 2 ! ⋅ ( S − a 1 − a 2 ) ! × ⋯ = S ! a 1 ! ⋅ a 2 ! ⋅ a 3 ! ⋯ a n ! \\begin{aligned} \\large &C_S^{a_1}\\cdot C_{S - a_1}^{a_2}\\cdot C_{S - a_1-a_2}^{a_3}\\cdots\\\\ &= \\frac{S!}{a_1 ! \\cdot (S - a_1)!} \\times \\frac{(S- a_1) !}{a_2 ! \\cdot (S - a_1 - a_2)!} \\times \\cdots\\\\ &= \\frac{S!}{a_1 ! \\cdot a_2 ! \\cdot a_3 ! \\cdots a_n !} \\end{aligned} CSa1CSa1a2CSa1a2a3=a1!(Sa1)!S!×a2!(Sa1a2)!(Sa1)!×=a1!a2!a3!an!S!

其实还可以换一种方法理解
先把这 S S S 个数随便排,有 S ! S! S! 种方案,但是因为有 a i a_i ai 个球颜色相同,所以要除以 a i a_i ai

可重集合

引入

n n n 种颜色的球,每种球有无限个,求选出 m m m 个球的方案

学过初赛的大佬都知道,直接隔板法就可以做

差不多就是把 m m m 个球分组,第几组代表第几类球选几个
也就是说在 m m m 个球中插入 n − 1 n-1 n1 个隔板,两个隔板之间的球表示每一类选几个
这样,相当于一共有 m + n − 1 m+n-1 m+n1 个空位,选出 m m m 个放球, n − 1 n-1 n1 个放隔板
方案数就是 C ( m + n − 1 ) ( n − 1 ) \\large C_{(m+n-1)}^{(n-1)} C(m+n1)(n1)

圆排列

引入

n n n 个人站成一圈,有几种站法

其实思路很简单,就是求出全排列然后再除以 n n n (因为一个圈可以有 n n n 种拆分方式)

错排问题

引入

求出1到 n n n 的排列中每个数都不在自己位置上的排列个数

这题也可以根据递推求解

假设第 n n n 个小朋友坐在了第 i i i 个小朋友的位置上,分成两个子问题:

  1. i i i 个小朋友也坐在第 n n n 个小朋友的位置上,问题就变成了求 n − 2 n-2 n2 个小朋友的错排
  2. i i i 个小朋友没有坐在第 n n n 个小朋友的位置上,问题变成了求 n − 1 n-1 n1 个小朋友的错排

那么就得到了递推式子:
f n = ( n − 1 ) × ( f n − 2 + f n − 1 ) f_n = (n-1) \\times (f_{n-2} + f_{n-1}) fn=(n1)×(fn2+fn1)

二项式定理

( x + y ) n = ∑ i = 0 n ( n i ) x i y n − i (x+y)^n = \\sum_{i=0}^n\\dbinom{n}{i}x^i y^{n-i} (x+y)n=i=0n(in)xiyni

证明

假设
( x + y ) n − 1 = ∑ i = 0 n − 1 ( n − 1 i ) x i y n − 1 − i (x+y)^{n-1} = \\sum_{i=0}^{n-1}\\dbinom{n-1}{i}x^i y^{n-1-i} (x+y)n1=i=0n1(in1)xiyn1i
( x + y ) n = ( x + y ) ( x + y ) n − 1 = ( x + y ) ∑ i = 0 n − 1 ( n − 1 i ) x i y n − 1 − i = ∑ i = 0 n − 1 ( n − 1 i ) x i + 1 y n − 1 − i + ∑ i = 0 n − 1 ( n − 1 i ) x i y n − i = ∑ i = 1 n ( n − 1 i − 1 ) x i y n − i + ∑ i = 0 n − 1 ( n − 1 i ) x i y n − i \\begin{aligned} &(x+y)^n = (x+y)(x+y)^{n-1}\\\\ &=(x+y)\\sum_{i=0}^{n-1}\\dbinom{n-1}{i}x^i y^{n-1-i}\\\\ &=\\sum_{i=0}^{n-1}\\dbinom{n-1}{i}x^{i+1} y^{n-1-i}+\\sum_{i=0}^{n-1}\\dbinom{n-1}{i}x^i y^{n-i}\\\\ &=\\sum_{i=1}^{n}\\dbinom{n-1}{i-1}x^{i} y^{n-i}+\\sum_{i=0}^{n-1}\\dbinom{n-1}{i}x^{i} y^{n-i} \\end{aligned} (x+y)n=(x+y)(x+y)n1=(x+y)i=0n1(in1)xiyn1i=i=0n1(in1)xi+1yn1i+i=0n1(in1)xiyni=i=1n(i1n1)xiyni+i=0n1(in1)xiyni
因为 ( n − 1 i − 1 ) + ( n − 1 i ) = ( n i ) \\dbinom{n-1}{i-1}+\\dbinom{n-1}{i}=\\dbinom{n}{i} (i1n1)+(in1)=(in),所以
( x + y ) n = ∑ i = 0 n ( n i ) x i y n − i (x+y)^n = \\sum_{i=0}^n\\dbinom{n}{i}x^i y^{n-i} (x+y)n=i=0n(in)xiyni

如何理解?

x + y x+y x+y 种颜色,给 n n n 个球染色,一共有 ( x + y ) n (x+y)^n (x+y)n种染色方法
而这 x + y x+y x+y 种颜色可以分为 x x x 种深色和 y y y 种浅色
我们枚举有 i i i 个球染成深色, n − i n-i ni 个球染成浅色
方案数就是 ( n − 1 i ) x i y n − i \\dbinom{n-1}{i}x^i y^{n-i} (in1)xiyni

卢卡斯定理

p p p 为质数时
( n m ) m o d p = ( [ n p ] [ m p ] ) ( n m o d p m m o d p ) m o d p \\dbinom{n}{m} mod p = \\dbinom{\\left[ \\frac{n}{p} \\right]}{\\left[ \\frac{m}{p} \\right]} \\dbinom{n \\mod p}{m \\mod p} \\mod p (mn)modp=([pm][pn])(mmodpnmodp)modp

例题卢卡斯定理

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define int ull
const int N = 1e6+10;
const int M = 1e4+10;
const int INF = 0x3f3f3f3f;using namespace std;
int ksm(int a, int b, int mod){int x = a,ans = 1;while(b){if(b & 1){ans = ans * x % mod;}x = x * x % mod;b >>= 1;}return ans;
}
ull C(int n, int m, int mod){if(n < m) return 0;int a =1, b = 1;for(int i = 1; i <= n; i++) a = a * i % mod;for(int i = 1; i <= m; i++) b = b * i % mod;for(int i = 1; i <= n - m; i++) b = b * i % mod;return a * ksm(b,mod-2,mod) % mod;
//	return a / b % mod;
}
ull lucas(int n, int m, int mod){if(n < mod && m < mod) return C(n,m,mod);return lucas(n/mod, m/mod,mod) * C(n % mod,m % mod,mod) % mod;
}
signed main(){int t;cin >> t;while(t--){int n, m, p;cin >> n >> m >> p;cout << lucas(n+m,n, p) << endl;}return 0;
}