基本计数原理
加法原理与分类计数法
要完成一件任务,可以经过n种相互无重叠的方案,若第i种方案的方法数为ai,那么完成这件任务的总方案数为
a
1
+
a
2
+
a
3
+
.
.
.
+
a
n
=
∑
i
=
1
n
a
i
a_1+a_2+a_3+...+a_n=\sum_{i=1}^n a_i
a1+a2+a3+...+an=i=1∑nai
乘法原理与分步计数法
要完成一件任务,经过n个步骤,若第i个步骤的方法数为ai,那么完成这件任务的总方案数为
a
1
∗
a
2
∗
a
3
∗
.
.
.
∗
a
n
=
∏
i
=
1
n
a
i
a_1*a_2*a_3*...*a_n=\prod_{i=1}^n a_i
a1∗a2∗a3∗...∗an=i=1∏nai
加法原理与乘法原理相结合
在具体题目中,加法原理和乘法原理常常要结合使用。
举例
假设所有道路单向,从甲地到乙地有两条路可走,从乙到丙地有三条路可走,又从甲地不经乙地直达丙地有三条路可走,问从甲地到丙地的不同走法有几种?
分析
从甲地到丙地有两种方案:经过乙地去丙地和直接去丙地,其中经过乙地去丙地可分为两个步骤:甲到乙,乙到丙。因此应用乘法原理,经过乙地去丙地的方案为2*3=6种,再应用加法原理,从甲地去丙地的方案为3+6=9种。
排列组合
排列数 A n m A_n^m Anm
定义
对自然数n,m(m≤n),从n个不同元素中,任取m个元素按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列。从n个不同元素中取出m个元素的所有排列的个数,叫做从n个不同元素中取出m个元素的排列数。
其中n=m时的排列数为n个数的全排列。
计算方法
对第一个数,有n种选择方案,第二个数有n-1种选择方案(除了第一个数均可选择),第三个数有n-2种(除了前两个选过的数均可选择),以此类推,第m个数有n-m+1种,根据乘法原理,可计算出总方案数为:
A
n
m
=
n
∗
(
n
−
1
)
∗
(
n
−
2
)
∗
…
∗
(
n
−
m
+
1
)
=
n
!
(
n
−
m
)
!
A_n^m=n*(n-1)*(n-2)*…*(n-m+1)=\frac{n!}{(n-m)!}
Anm=n∗(n−1)∗(n−2)∗…∗(n−m+1)=(n−m)!n!
其中
n
!
n!
n!表示n的阶乘,即从1乘到n的结果。例如,
6
!
=
6
∗
5
∗
4
∗
3
∗
2
∗
1
6!=6*5*4*3*2*1
6!=6∗5∗4∗3∗2∗1。规定
0
!
=
1
0!=1
0!=1。
组合数 C n m C_n^m Cnm
定义
对自然数n,m(m≤n),从n个不同元素中,任取m个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。
计算方法
先求出排列数
A
n
m
A_n^m
Anm,再除以m个数的全排列
A
m
m
A_m^m
Amm(去掉其中的顺序)。
C
n
m
=
A
n
m
A
m
m
=
n
!
(
n
−
m
)
!
∗
m
!
=
n
∗
(
n
−
1
)
∗
(
n
−
2
)
∗
.
.
.
∗
(
n
−
m
+
1
)
m
∗
(
m
−
1
)
∗
(
m
−
2
)
∗
.
.
.
∗
1
C_n^m=\frac{A_n^m}{A_m^m}=\frac{n!}{(n-m)!*m!}=\frac{n*(n-1)*(n-2)*...*(n-m+1)}{m*(m-1)*(m-2)*...*1}
Cnm=AmmAnm=(n−m)!∗m!n!=m∗(m−1)∗(m−2)∗...∗1n∗(n−1)∗(n−2)∗...∗(n−m+1)
来自百度百科对这个公式的解释:
组合公式的推导是由排列公式去掉重复的部分而来的,排列公式是建立一个模型,从n个不相同元素中取出m个排成一列(有序),而组合公式对应另一个模型,取出m个成为一组(无序),由于m个元素组成的一组可以有m!种不同的排列(m个数的全排列 A m m A_m^m Amm),组合的总数就是 C n m = A n m A m m C_n^m=\frac{A_n^m}{A_m^m} Cnm=AmmAnm
性质
-
C
n
m
=
C
n
n
−
m
C_n^m=C_n^{n-m}
Cnm=Cnn−m,即从n个不同元素中取出m个元素的组合数=从n个不同元素中取出 (n-m) 个元素的组合数。
例如C(9,2)=C(9,7),即从9个元素里选择2个元素的方法与从9个元素里选择7个元素(即剔除两个元素)的方法是相等的。
从组合数公式也可以得出这个性质:
C n m = n ! ( n − m ) ! ∗ m ! C n n − m = n ! [ n − ( n − m ) ] ! ∗ ( n − m ) ! = n ! m ! ∗ ( n − m ) ! = C n m C_n^m=\frac{n!}{(n-m)!*m!}\\ C_n^{n-m}=\frac{n!}{[n-(n-m)]!*(n-m)!}=\frac{n!}{m!*(n-m)!}=C_n^m Cnm=(n−m)!∗m!n!Cnn−m=[n−(n−m)]!∗(n−m)!n!=m!∗(n−m)!n!=Cnm -
C
n
m
=
C
n
−
1
m
−
1
+
C
n
−
1
m
C_n^m=C_{n-1}^{m-1}+C_{n-1}^{m}
Cnm=Cn−1m−1+Cn−1m递推公式
以下是来自百度百科的解释:
等式左边表示从n个元素中选取m个元素,而等式右边表示这一个过程的另一种实现方法:任意选择n中的某个备选元素为特殊元素,从n中选m个元素可以由此特殊元素的被包含与否分成两类情况,即m个被选择元素包含了特殊元素和m个被选择元素不包含该特殊元素。前者相当于从n-1个元素中选出m-1个元素的组合,即$C_{n-1}^{m-1}$;后者相当于从n-1个元素中选出m个元素的组合,即$C_{n-1}^{m}$。
这种方法可以理解为:取其中一个元素为特殊元素,记为a,则从n个元素中选m个就可划分为包含a和不包含a两种情况,即从剩余的n-1种元素中选m-1个和m个,分别为 C n − 1 m − 1 , C n − 1 m C_{n-1}^{m-1},C_{n-1}^{m} Cn−1m−1,Cn−1m。
另外一种理解方式(利用杨辉三角理解):利用组合数与杨辉三角的关系,将杨辉三角编号,从0开始,则第i行的第j个数就等于 C i j C_{i}^j Cij,杨辉三角的每一项等于它两肩上数之和,即 C i − 1 j − 1 C_{i-1}^{j-1} Ci−1j−1与 C i − 1 j C_{i-1}^j Ci−1j两个数,即为此公式。 -
C
n
−
m
0
+
C
n
−
m
+
1
1
+
C
n
−
m
+
2
2
+
.
.
.
+
C
n
m
=
C
n
+
1
m
C_{n-m}^{0}+C_{n-m+1}^{1}+C_{n-m+2}^{2}+...+C_{n}^{m}=C_{n+1}^{m}
Cn−m0+Cn−m+11+Cn−m+22+...+Cnm=Cn+1m
或者另一种表示: C k k + C k + 1 k + C k + 2 k + . . . + C n k = C n + 1 k + 1 C_{k}^{k}+C_{k+1}^{k}+C_{k+2}^{k}+...+C_{n}^{k}=C_{n+1}^{k+1} Ckk+Ck+1k+Ck+2k+...+Cnk=Cn+1k+1
可以通过性质2将等式右边拆解,再不断拆解刚刚拆出的第一项得到(等号左侧第一项的转化: C n − m 0 = 1 = C n − m + 1 0 C_{n-m}^{0}=1=C_{n-m+1}^{0} Cn−m0=1=Cn−m+10)。 -
C
n
0
+
C
n
1
+
C
n
2
+
.
.
.
+
C
n
n
=
2
n
C_{n}^{0}+C_{n}^{1}+C_{n}^{2}+...+C_{n}^{n}=2^n
Cn0+Cn1+Cn2+...+Cnn=2n
杨辉三角一行求和,利用二项式定理 ( a + b ) n = ∑ i = 0 n C n i a n − i b i (a+b)^n=\sum_{i=0}^nC_n^ia^{n-i}b^i (a+b)n=∑i=0nCnian−ibi,代入a=b=1,得等式右侧为组合数求和,等式左侧为2n,即为上式。
同样可利用二项式定理求许多与单行组合数有关的式子。
求值方法
直接求值
这里说的直接求值指的是题目保证结果在unsigned int内(换句话说,运算过程一定不会超出long long unsigned int的范围)的情况下,不进行取模直接计算组合数的方法。
C
n
m
=
n
∗
(
n
−
1
)
∗
(
n
−
2
)
∗
.
.
.
∗
(
n
−
m
+
1
)
m
∗
(
m
−
1
)
∗
(
m
−
2
)
∗
.
.
.
∗
1
C_n^m=\frac{n*(n-1)*(n-2)*...*(n-m+1)}{m*(m-1)*(m-2)*...*1}
Cnm=m∗(m−1)∗(m−2)∗...∗1n∗(n−1)∗(n−2)∗...∗(n−m+1)
注:ll代表long long,即
typedef long long ll;
ll C(ll x,ll y)//x代表n,y代表m
{
ll b=1;
for(int i=1;i<=min(y,x-y);i++)//利用性质1减少循环次数
b=b*(x-i+1)/i;//乘上式分子从前往后数的第i项,除以分母从后往前数的第i项
return b;
}
为什么可以直接除,不会出现b中无i这个因子的情况吗?
因为对于任意的i,b里已经乘过i个连续自然数,在i个连续自然数中,必然存在一个i的倍数(因为两个相邻的i的倍数最多间隔为i-1),因此b中一定会有i这个因子。
对质数取模的前提下求值
当题目要求取模的时候,通常情况下我们无法直接做(直接搞会爆long long),那就需要边计算边取模。按照刚刚的方法,不难写出以下的WA代码:
ll C(ll x,ll y)
{
ll b=1;
for(int i=1;i<=min(y,x-y);i++)
b=b*(x-i+1)/i%mod;
return b;
}
由于计算过程边计算边取模,此时b未必能够被a整除,比如求 C 6 3 m o d    11 C_6^3\mod11 C63mod11,如果直接求值,则会进行这样的步骤:(为了直观将每次循环改写为一个步骤展示)
不难计算出
C
6
3
=
6
∗
5
∗
4
3
∗
2
∗
1
=
20
,
20
m
o
d
  
11
=
9
C_6^3=\frac{6*5*4}{3*2*1}=20,20\mod11=9
C63=3∗2∗16∗5∗4=20,20mod11=9,即刚刚的算法出现了错误。
错误的原因就在第三次:由于乘积b对mod进行过取模操作,b%mod不一定能够被i整除了,因此我们需要用到逆元,化除法为乘法,避免上面出现的问题。
关于逆元:贴个博客,提到了多种求逆元的方法,我这里求逆元使用的是费马小定理:
ll qpow(ll a,ll b)
{
ll k=a,ans=1;
while(b)
{
if(b&1) ans=(ans*k)%mod;
k=k*k%mod;
b>>=1;
}
return ans;
}
ll inv(int a)
{
return qpow(a,mod-2);
}
于是上述代码可改写为:
ll C(ll x,ll y)
{
ll b=1;
for(int i=1;i<=min(y,x-y);i++)
b=b*(x-i+1)*inv(i)%mod;
return b;
}
然而这样我们每一次都要求i的逆元,复杂度较高,可以将需要除去的数的乘积计算好,对乘积求一次逆元,可以节省大量求逆元的时间。
ll C(int x,int y)
{
ll a=1,b=1;
for(int i=1;i<=min(y,x-y);i++)
{
a=a*i%mod;//计算分母
b=b*(x-i+1)%mod;//计算分子
}
return b*inv(a)%mod;//计算b/a并返回
}
Lucas定理
当求组合数时,m、n很大的时候,一步步进行乘法运算复杂度较高,耗时较长,需要用更快的算法进行运算:
内容
对于非负整数n,m 和 质数 p 有
C
n
m
=
∏
(
C
n
i
m
i
m
o
d
  
p
)
C_n^m=\prod( C_{n_i}^{m_i} \mod p)
Cnm=∏(Cnimimodp)
其中
n
i
n_i
ni 和
m
i
m_i
mi 分别为 n 和 m 的p进制表示的系数的第i位
若 n<m 则认为
C
n
m
=
0
C_n^m=0
Cnm=0
该方法适用于m、n很大同时p较小的情况,可以节省大量求组合数的时间。
代码实现
通过Lucas,原本求
C
n
m
C_n^m
Cnm转换成了求
C
n
i
m
i
C_{n_i}^{m_i}
Cnimi,所以代码实现的关键在于求
n
i
n_i
ni与
m
i
m_i
mi。
n
i
n_i
ni的意义:n的p进制表示的系数的第i位
n
1
n_1
n1的求法:
n
m
o
d
  
p
n \mod p
nmodp
n
2
n_2
n2的求法:
n
p
m
o
d
  
p
\frac{n}{p}\mod p
pnmodp
以此类推,
n
i
n_i
ni可由
n
p
i
−
1
m
o
d
  
p
\frac{n}{p^{i-1}}\mod p
pi−1nmodp求得
同理,
m
i
m_i
mi可由
m
p
i
−
1
m
o
d
  
p
\frac{m}{p^{i-1}}\mod p
pi−1mmodp求得
实际上,可以利用递归调用求得每一项的结果:
ll getc(int x,int y)
{
if(x<y) return 0;
if(y==0) return 1;//边界
return getc(x/mod,y/mod)*C(x%mod,y%mod)%mod;
}
解释一下return中的两项:
当第i次递归调用此函数时:
C(x%mod,y%mod)
:x%mod为
x
i
x_i
xi,y%mod为
y
i
y_i
yi,即此项直接调用前面求组合数的函数求出
C
n
i
m
i
C_{n_i}^{m_i}
Cnimi
getc(x/mod,y/mod)
:x/mod为
x
p
i
\frac{x}{p^{i}}
pix,y/mod为
y
p
i
\frac{y}{p^{i}}
piy,即递归调用getc函数求第i+1位及以上的组合数