zoukankan      html  css  js  c++  java
  • 排列组合与二项式基础

    排列组合相关与二项式基础

    一、加乘原理

    1.加法原理

    做一件事,完成他的方法可以分为(n)个互不相交的类,每一类有(a_i)中方法,则完成这件事一共有

    [a_1+a_2+...+a_n ]

    种方法。

    加法原理的核心思想就是,把“整体”分作每个“部分”,分别对每个部分进行计数,最后求和。

    2.乘法原理

    若完成一件事有(n)个步骤,每个步骤有(a_i)种方法,则完成这件事一共有

    [a_1 imes a_2 imes ... imes a_n ]

    种方法。

    乘法原理的核心思想就是,把这件事分成几个步骤,且每一个步骤的方法数目比较好确定。

    加乘原理很简单,你们小学奥数学的都比本蒟蒻强,但加乘原理是计数的基础。

    二、排列与组合

    1.排列

    一般地,从(n)个不同元素中取出(m(m≤n))个元素,按照一定的顺序排成一列,叫做从(n)个元素中取出(m)个元素的一个排列,记作(A_n^m)(或者记作(P_n^m))。

    由乘法原理得:

    [A_n^m=n imes(n-1) imes (n-2) imesdots imes (n-m+1) ]

    特别地,如果(m=n),则我们就可以得到关于(n)全排列

    [A_n^n=n imes(n-1) imes(n-2) imes dots imes 1=n! ]

    我们约定(0!=1),则排列公式可写为

    [A_n^m=frac{n!}{(n-m)!} ]

    这也是这个公式的更为常见的形式。

    2.组合

    一般地,从(n)个不同的元素中,任取(m(m≤n))个元素为一组,叫作从(n)个不同元素中取出(m)个元素的一个组合,记作(C_n^m)(或者记作(inom{n}{m})

    组合和排列数的区别是,组合数要求无序

    所以我们可以得到:

    [C_n^m=frac{A_n^m}{m!} ]

    展开我们可以得到:

    [C_n^m=frac{n!}{m!(n-m)!} ]

    关于排列和组合还有一些经典的数学问题,由于纯数学不是我们讨论的重点,这里仅给出结论,需要证明的可以自行在网上查阅。

    1.可重排列:(n^m)

    2.有限元素的可重排列:(frac{n!}{n_1! imes n_2! imes dots imes n_m!})

    3.可重组合:(C_{n+m-1}^m)

    4.有限元素的可重组合:(C_{m+k-1}^{k-1})(这里(k)小于等于每个元素的数量)

    三、二项式相关

    1.二项式系数与二项式定理

    组合数(C_n^m)也被称作二项式系数,它有三重面目:

    1.组合意义:(n)个不同元素的(m)-组合

    2.显示表示:(C_n^m=frac{n!}{m! imes (n-m)!})

    3.二项展开式的系数,即有恒等式

    [(x+y)^n=sum^n_{i=0}C_n^i imes x^n imes y^{n-i} ]

    上面的恒等式称为二项式定理。

    二项式定理的证明有很多种,这里给出一种基于其组合意义的证明:

    显然

    [(x+y)^n=egin{matrix}underbrace{(x+y)(x+y)dots(x+y)} \ n个(x+y) end{matrix} ]

    对于其中的一项(x^iy^{n-i}),相当于是从(n)((x+y))中任意取(i)个,从中挑出(x),再在剩下的((x+y))中挑出(y)来相乘,所以该项的系数是(C_n^i),由此不难验证二项式定理。

    2.组合恒等式

    常见的组合恒等式有以下几个,它们大多都和二项式相关

    1.(对称性

    [C_n^m=C_n^{n-m} ]

    2.(递推公式

    [C_n^m=C_{n-1}^m+C_{n-1}^{m-1} ]

    3.

    [C_n^0+C_n^1+C_n^2+dots+C_n^n=2^n ]

    4.(单峰性)若(n)是偶数,则

    [C_n^0<C_n^1<dots<C_n^{frac{n}{2}}>dots>C_n^{n-1}>C_n^n ]

    (n)是奇数,则

    [C_n^0<C_n^1<dots<C_n^{frac{n-1}{2}}=C_n^{frac{n+1}{2}}>dots>C_n^{n-1}>C_n^n ]

    5.

    [C_n^0+C_{n+1}^1+dots+C_{n+k}^k=C_{n+k+1}^{k+1} ]

    6.

    [C_n^0-C_n^1+C_n^2+dots+(-1)^nC_n^n=0 ]

    7.(范德蒙恒等式

    [C_{m}^0C_n^k+C_m^1C_n^{k-1}+C_m^2C_n^{k-2}+dots+C_m^kC_n^0=C_{n+m}^k ]

    8.

    [C_n^k=frac{n}{k}C_{n-1}^{k-1} ]

    9.

    [C_n^kC_k^m=C_n^mC_{n-k}^{m-k} ]

    它们的证明如下:

    1.从(n)个数中选(m)个数,他们呢构成了一个,剩下没选的数也构成了一个集合,等式一显然成立。

    2.假设我们现在面对的是第(n)号元素,我们无非就两种选择:选和不选。

    如果我们选择第(n)号元素,那么我们就需要在前(n-1)个元素中选(m-1)个元素。

    如果我们不选择第(n-1)号元素,那么我们就需要在前(n-1)一元素中选(m)个元素。

    由加法原理不难验证等式2的正确性。

    3.从(n)个元素中取任意个元素,要么选,要么不选,所以是(2^n)种。

    我们也可以看成取0个,取1个, 取2个,...,取(n)个,就相当于等式的左边。

    所以等式3成立。

    4.我们完全可以通过暴力展开的形式来证明,或者我们结合性质1也不难证明

    5.反复运用递推公式,我们有:

    [C_n^0+C_{n+1}^1=C_{n+1}^0+C_{n+1}^1=C_{n+2}^1\ C_{n+2}^1+C_{n+2}^2=C_{n+3}^2\ C_{n+3}^2+C_{n+3}^3=C_{n+4}^3\ cdots\ C_{n+k}^{k-1}+C_{n+k}^k=C_{n+k+1}^k ]

    6.在二项式定理中取(x=1)(y=-1)即可。

    推论:

    [C_n^0+C_n^2+C_n^4+dots=C_n^1+C_n^3+C_n^5+dots ]

    7.同样地,我们仍然可以暴力展开证明,但很麻烦。

    我们仍然考虑从它的组合意义上证明。

    如果我们从(n)个zrw和(m)个lsy中任意选取(k)个人组成一组,显然,答案为(C_{n+m}^k)

    所有的方案可以分为(k+1)个类:第(i)类由(i)个zrw和(k-i)个lsy组成一组((iin[0,k]且iin )),每一类的答案就是(C_n^iC_m^{k-i}),根据加法原理不难验证等式7的正确性。

    8&9.暴力展开得

    [egin{align} C_n^kC_k^m &=frac{n!}{k!(n-k)!} imesfrac{k!}{m!(k-m)!}\ &=frac{n!}{m!(n-m)!} imesfrac{(n-m)!}{(k-m)!(n-k)!}\ &=C_n^mC_{n-k}^{m-k} end{align} ]

    (k=1)时即为等式8

    3.二项式反演

    这部分可以看看本蒟蒻的一篇博客:二项式反演笔记

    四、组合数的求法

    1.递推公式法

    我们先令(C_i^0=1),通过组合数的递推公式

    [C_n^m=C_{n-1}^m+C_{n-1}^{m-1} ]

    即可求得

    时间复杂度为(O(n^2))

    for(int i=0;i<=n;i++) c[i][0]=1;
    for(int i=1;i<=n;i++){
    	for(int j=1;j<=i;j++)
    		c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
    

    2.显示表示法

    注:除第一种方法意外,其他所有方法均是在模数意义下求组合数的值

    [C_n^m=frac{n!}{m!(n-m)!} ]

    我们可以先求分子(n!)的值,然后对分母(m!(n-m)!)来求模(p)意义下的逆元,最后相乘即可。

    时间复杂度为(O(n))

    如果我们是批量求组合数的话,我们可以做到更优。

    我们先预处理(i!)以及(i!)(p)意义下的逆元,时间复杂度为(O(n))

    (求逆元时第一个数为(O(log n)),之后根据阶乘逆元的性质我们可以在(O(n))的时间内求得)

    然后我们可以在(O(1))的时间内回答询问:f[n] * invf[m]%p * invf[n-m]%p

    关于预处理阶乘逆元,有

    [invf_{i} equiv invf_{i+1} imes (i+1) pmod p ]

    证明:

    [f_i imes invf_i equiv 1 pmod p ]

    [f_{i-1} imes i imes invf_i equiv 1 pmod p ]

    3.Lucas定理

    (p)是质数,则对于(1le mle n),有

    [C_n^mequiv C_{n mod p} ^{m mod p} imes C_{frac{n}{p}}^{frac{m}{p}} pmod p ]

    也就是把(n)(m)进行(p)进制分解,然后分别求它们每一位对应的组合数的值。

    我们可以用类似于生成函数的方法来证明Lucas定理。

    (n=ap+r_1)(m=bp+r_2)

    我们取这样一个式子:

    [egin{align} (1+x)^n &=(1+x)^{ap+r_1}\ &=((1+x)^p)^a imes (1+x)^{r_1} end{align} ]

    (p)为质数时,对于(iin[1,p-1]且iin N^*),我们可以得到

    [C_p^iequiv0 pmod p ]

    (结合组合数的显示表示不难证明)

    于是

    [egin{align} ((1+x)^p)^a imes (1+x)^{r_1}&equiv(1+x^p)^a imes(1+x)^{r_1}\ &equivsum_{i=0}^aC_a^ix^{pi} imessum_{j=0}^{r_1}C_{r_1}^jx^{r_1} pmod p end{align} ]

    (结合二项式定理不难理解)

    整理得

    [sum_{i=0}^nC_n^ix^iequivsum_{i=0}^aC_a^ix^{pi} imessum_{j=0}^{r_1}C_{r_1}^jx^{r_1} pmod p ]

    对于左边展开的一项(x^{bp+r_2}),他对应的系数是(C_n^{bp+r_2})

    而当且仅当右边的(i=b)(j=r_2)时才能对应左边的(x^{bp+r_2}),此事它所对应的系数为(C_a^b imes C_{r_1}^{r_2})

    整理得

    [C_n^mequiv C_{n mod p} ^{m mod p} imes C_{frac{n}{p}}^{frac{m}{p}} pmod p ]

    证毕。

    Lucas定理一般用于模数为质数,且(n)(m)都较大时。它需要与上面得两种方法配合起来使用。

    来道模板题吧:P3807 【模板】卢卡斯定理

    Code:

    #include<bits/stdc++.h>
    #define FUCK puts("FUCK")
    #define int long long
    using namespace std;
    const int maxn=1e6+5;
    int T;
    int n,m,p;
    int f[maxn];
    inline int ksm(int x,int y){
    	if(!y) return 1;
    	if(y==1) return x%p;
    	int tmp=ksm(x,y/2);
    	tmp=(tmp*tmp)%p;
    	if(y&1) return (tmp*x)%p;
    	else return tmp;
    }
    inline int calc(int x,int y){
    	if(y>x) return 0;
    	return f[x]*ksm(f[y],p-2)%p*ksm(f[x-y],p-2)%p;
    }
    inline int Lucas(int x,int y){
    	if(!y) return 1;
    	return (calc(x%p,y%p)*Lucas(x/p,y/p))%p;
    }
    signed main(){
    	scanf("%lld",&T);
    	while(T--){
    		scanf("%lld%lld%lld",&n,&m,&p);
    		f[0]=1;
    		for(int i=1;i<=p;i++) f[i]=(f[i-1]*i)%p;
    		printf("%lld
    ",Lucas(n+m,n));
    	}
    	return 0;
    }
    

    另外,求组合数还有一种方法:exLucas。不过这种方法相对上面的方法比较难写,一般直接用Lucas就够了。

    如有不足尽请指正,谢谢!

    参考资料:

    1.华东师范大学出版社 《奥数教程》高中第三分册

    2.李煜东 《算法竞赛进阶指南》

  • 相关阅读:
    Java集合源码分析(一)
    EffectiveJava——请不要在代码中使用原生态类型
    Dubbo初探
    EffectiveJava——用函数对象表示策略
    EffectiveJava——类层次优于标签类
    notebook1.md
    NoteBook学习(二)-------- Zeppelin简介与安装
    Spark2.0学习(三)--------核心API
    Spark2.0学习(二)--------RDD详解
    Spark2.0学习(一)--------Spark简介
  • 原文地址:https://www.cnblogs.com/ybwowen/p/11216720.html
Copyright © 2011-2022 走看看