zoukankan      html  css  js  c++  java
  • 组合数的研究

    介绍

    组合数学算是数学中比较难(虽然被很多人看不起)的一个分类了。
    我们可以用组合数学解决很多方案数有关的问题。

    定义

    组合数学里面有两个大的块。
    一个是排列,一个是组合。
    所谓排列就是在一个集合中取出的有序子集。
    所谓组合就是在一个集合中取出的无序子集。
    定义可能看不懂,但举个例子就很简单了。
    比如有n个人,我们从中取出m个人。
    把他们排成队,就是排列了。如果不关心顺序,只关心取出哪些人就是组合了。
    下面介绍排列组合的几个基本定理和公式。
    国际上一般记组合数为$inom{n}{m} $
    但是这里我习惯用国内的记法(C_{n}^{m})

    公式

    基本计算公式

    1.排列数计算公式

    [A_{n}^{m}=frac{m!}{n!} ]

    2.组合数计算公式

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

    [C_{n}^{m} = frac{prod_{i=n}^{n-m+1}i}{m!} ]

    注意这里!表示阶乘,第二个公式是手动化简公式。

    3.组合数递推公式

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

    证明很简单,在n个元素里取m个元素的方案数=取第m个元素的方案数+不取第m个元素的方案数。

    4.卢卡斯(Lucas)定理

    [C_{n}^{m} \%p =C_{n\%p }^{m\%p } imes C_{n/p}^{m/p} ]

    注意,这里p必须是质数。
    公式不长记住就行,懒得证明。

    5.组合数的和

    [sum{C_n^i}(0le ile n)=2^n ]

    算法

    暴力算法

    暴力组合数

    int C(int n,int k){
    	int ans=1;
    	for(int i=n;i>n-k;i--)ans*=i;
    	for(int i=1;i<=k;i++)ans/=i;
    	return ans;
    }
    

    杨辉三角求组合数

    void init(){
    	sj[0][0]=1;
    	for(int i=1;i<=2004;i++){
    		sj[i][0]=1;
    		for(int j=1;j<=i;j++)
    			sj[i][j]=(sj[i-1][j]+sj[i-1][j-1])%mod;
    	}
    }
    int C(int m,int k){
    	return sj[m][k];
    }
    

    线性组合数

    (前置技能:线性求逆元)

    我们发现$$C_{n}^{m} = frac{n!}{m!(n-m)!}$$
    记$$A[n]=n!%p$$

    [B[n]=prod_{i=1}^{n}{inv[n]}%p ]

    [C_{n}^{m} = A[n] imes B[m] imes B[n-m] ]

    我们只要线性预处理出阶乘数组和逆元前缀积数组就可以了。
    先求出1到n的所有数在模p意义下的逆元。

    [inv[i]=(p-frac{p}{i}) imes inv[p\%i]\%p ]

    [B[i+1]=B[i] imes inv[i+1]%p ]

    [A[i+1]=A[i] imes (i+1)%p ]

    这样子我们就可以直接计算了

    int fac[100009],inv[100009],n=100005;
    void init(){
    	inv[0]=inv[1]=fac[0]=1;
    	for(int i=2;i<=n;i++)
    		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    	for(int i=2;i<=n;i++)inv[i]=1ll*inv[i-1]*inv[i]%mod;
    	for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
    }
    int C(int n,int m){
    	return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
    }
    int main()
    {
    	init();
    	cout<<C(5,2)<<endl;
    	return 0;
    }
    

    卢卡斯定理

    卢卡斯定理用于解决n,m特别大而模数p不是很大的情况。
    利用卢卡斯定理,在(C_{n}^{m})特别大的时候不断分解,当n,m小于p时进行组合运算。

    int Lucas(int n,int m,int p){
    	if(m>n)return 0;
    	if(n<p)return C(n,m);
    	else return Lucas(n/p,m/p,p)*Lucas(n%p,m%p,p)%p;
    }
    

    拓展卢卡斯定理

    留坑待填
    未完待续...

  • 相关阅读:
    系统设计5:Google三剑客
    lintcode亚麻九题
    设计模式17:单例模式
    设计模式16:迭代器模式
    设计模式15:组合模式
    476. Number Complement
    561. Array Partition I
    627. Swap Salary
    617. Merge Two Binary Trees
    728. Self Dividing Numbers
  • 原文地址:https://www.cnblogs.com/onglublog/p/9883154.html
Copyright © 2011-2022 走看看