基本计数、排列组合与Lucas定理

本文介绍了基本的计数原理,包括加法原理与分类计数法、乘法原理与分步计数法以及两者结合的应用。接着详细探讨了排列与组合的概念、计算方法和性质,并特别讲解了Lucas定理的内容及其在大数计算中的应用,提供了一种高效求解大组合数的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本计数原理

加法原理与分类计数法

要完成一件任务,可以经过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=1nai

乘法原理与分步计数法

要完成一件任务,经过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 a1a2a3...an=i=1nai

加法原理与乘法原理相结合

在具体题目中,加法原理和乘法原理常常要结合使用。

举例

假设所有道路单向,从甲地到乙地有两条路可走,从乙到丙地有三条路可走,又从甲地不经乙地直达丙地有三条路可走,问从甲地到丙地的不同走法有几种?

甲地 乙地 丙地 有两条路 有三条路 所有道路单向, 不存在包含关系 有三条路 甲地 乙地 丙地
分析

从甲地到丙地有两种方案:经过乙地去丙地和直接去丙地,其中经过乙地去丙地可分为两个步骤:甲到乙,乙到丙。因此应用乘法原理,经过乙地去丙地的方案为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(n1)(n2)(nm+1)=(nm)!n!
其中 n ! n! n!表示n的阶乘,即从1乘到n的结果。例如, 6 ! = 6 ∗ 5 ∗ 4 ∗ 3 ∗ 2 ∗ 1 6!=6*5*4*3*2*1 6!=654321。规定 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=(nm)!m!n!=m(m1)(m2)...1n(n1)(n2)...(nm+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

性质
  1. C n m = C n n − m C_n^m=C_n^{n-m} Cnm=Cnnm,即从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=(nm)!m!n!Cnnm=[n(nm)]!(nm)!n!=m!(nm)!n!=Cnm
  2. 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=Cn1m1+Cn1m递推公式
    以下是来自百度百科的解释:
    等式左边表示从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} Cn1m1,Cn1m
    另外一种理解方式(利用杨辉三角理解):利用组合数与杨辉三角的关系,将杨辉三角编号,从0开始,则第i行的第j个数就等于 C i j C_{i}^j Cij,杨辉三角的每一项等于它两肩上数之和,即 C i − 1 j − 1 C_{i-1}^{j-1} Ci1j1 C i − 1 j C_{i-1}^j Ci1j两个数,即为此公式。
  3. 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} Cnm0+Cnm+11+Cnm+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} Cnm0=1=Cnm+10)。
  4. 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=0nCnianibi,代入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(m1)(m2)...1n(n1)(n2)...(nm+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 &ThinSpace;&ThinSpace; 11 C_6^3\mod11 C63mod11,如果直接求值,则会进行这样的步骤:(为了直观将每次循环改写为一个步骤展示)

Created with Raphaël 2.2.0 算法开始,b=1 b=(1*6/1)%11=6 b=(6*5/2)%11=15%11=4 b=(4*4/3)%11=(16/3)%11=5 算法结束,b=5

不难计算出 C 6 3 = 6 ∗ 5 ∗ 4 3 ∗ 2 ∗ 1 = 20 , 20 m o d &ThinSpace;&ThinSpace; 11 = 9 C_6^3=\frac{6*5*4}{3*2*1}=20,20\mod11=9 C63=321654=2020mod11=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 &ThinSpace;&ThinSpace; 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 &ThinSpace;&ThinSpace; p n \mod p nmodp
n 2 n_2 n2的求法: n p m o d &ThinSpace;&ThinSpace; p \frac{n}{p}\mod p pnmodp
以此类推, n i n_i ni可由 n p i − 1 m o d &ThinSpace;&ThinSpace; p \frac{n}{p^{i-1}}\mod p pi1nmodp求得
同理, m i m_i mi可由 m p i − 1 m o d &ThinSpace;&ThinSpace; p \frac{m}{p^{i-1}}\mod p pi1mmodp求得
实际上,可以利用递归调用求得每一项的结果:

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位及以上的组合数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值