跳转至

组合数

约 887 个字 26 行代码 预计阅读时间 3 分钟

讲课链接

排列组合

排列就是指从给定个数的元素中取出指定个数的元素进行排序

组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序

排列组合的中心问题是研究给定要求的排列和组合可能出现的情况总数。

加法原理

做一件事情,完成它有n类办法,ai 表示第i类方法的数目,那么完成这件事共有ni=1ai 种不同的方法

乘法原理

做一件事情,完成它有n个步骤,ai 表示第i个步骤的方法数目,那么完成这件共事有ni=1ai种不同的方法。

排列数

n 个数选 m 个排列,情况总数=Amn=n!(nm)!

全排列,指 n 个数选 n 个排列,情况总数=Ann=n!

组合数

n 个数选 m 个组合,情况总数=Cmn=n!m!(nm)!

组合数也常用(nm)表示

组合数也被称为“二项式系数”

多重集

包含重复元素的广义集合。

S={n1a1,n2a2,,nkak}

表示由 n1a1n2a2,…,nkak 组成的多重集

多重集的排列数

S 中选择 r 个元素排列

n!ki=1(ni!)

相当于把相同元素的排列数除掉了

多重集的组合数

S 中选择 r 个元素组合

这个问题等价于 x1+x2++xk=r 的非负整数解的数目,可以用插板法解决,答案为

(r+k1k1)

不相邻的排列

1nn 个自然数中选 k 个,这 k 个数中任何两个数都不相邻的组合有 (nk+1k) 种。

圆排列

n 个人全部来围成一圈,所有的排列数记为 Qnn。考虑其中已经排好的一圈,从不同位置断开,又变成不同的队列。 所以有

Qnn×n=AnnQn=Annn=(n1)!

由此可知部分圆排列的公式:

Qrn=Arnr=n!r×(nr)!

组合数性质

(nm)=(nnm)

相当于将选出的集合对全集取补集,故数值不变。(对称性)

(nk)=nk(n1k1)

由定义导出的递推式。

(nm)=(n1m)+(n1m1)

组合数的递推式(杨辉三角的公式表达)

(n0)+(n1)++(nn)=ni=0(ni)=2n

这是二项式定理的特殊情况。取 a=b=1 就得到上式。

mi=0(ni)(mmi)=(m+nm)   (nm)

拆组合数的式子,在处理某些数据结构题时会用到。

ni=0(ni)2=(2nn)

这是 (5) 的特殊情况,取 n=m 即可。

ni=0i(ni)=n2n1

带权和的一个式子,通过对 (3) 对应的多项式函数求导可以得证。

ni=0i2(ni)=n(n+1)2n2

与上式类似,可以通过对多项式函数求导证明。

nl=0(lk)=(n+1k+1)

在恒等式证明中较常用。

(nr)(rk)=(nk)(nkrk)

通过定义可以证明。

ni=0(nii)=Fn+1

其中 F 是斐波那契数列。

预处理组合数

const int N=1e6+3;
long long fac[N],inv[N];
long long fast_power(long long base, long long exponent)
{
    long long result = 1;
    for (; exponent > 0; exponent >>= 1)
    {
        if (exponent & 1)
            result = result * base % mod;
        base = base * base % mod;
    }
    return result;
}
void getC()
{
    fac[0]=1;
    for(int i=1;i<N;++i)
        fac[i]=fac[i-1]*i%mod;
    inv[N-1]=fast_power(fac[N-1],mod-2);
    for(int i=N-1;i;--i)
        inv[i-1]=inv[i]*i%mod;
}
long long C(int n,int m)
{
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}