zoukankan      html  css  js  c++  java
  • 组合数

    组合数

    其实我也没想到我第一篇博客会讲一个偏数学的内容。
    主要是我太弱了,只会这个

    何为组合数

    记号$C_n^m$表示组合数,其意义为在$n$个可区分物品中无序地选择$m$个物品的方案数。
    如三个数分别为$1,2,3$,希望选出两个数,则有$(1,2)(1,3)(2,3)$三种方案。
    因为是无序选择,因此$(1,2)$和$(2,1)$被认为是同一种方案。
    因而$C_3^2=2$.

    组合数的简单性质

    根据意义,易知:
    $C_n^0=1$
    $C_n^1=n$
    $C_n^n=1$
    $C_ni=C_n{n-i}$
    $C_n^m=0(m>n)$
    $sum_{i=1}nC_ni=2^n$

    组合数的计算方法

    根据组合数的定义,我们可以得到计算公式。
    $$C_n^m=frac{n(n-1)cdots(n-m+1)}{m!}=frac{n!}{m!(n-m)!}$$
    即统计有序选择的方案数$n(n-1)cdots(n-m+1)$,然后除掉每个方案被重复计数的$m!$。
    有了这个式子后,我们只要处理出阶乘及其倒数,便可以计算组合数。
    由于组合数一般很大,实际上大多时候在模意义下计算。

    组合数还存在递推式。
    即$$C_nm=C_{n-1}{m-1}+C_{n-1}^m$$
    从组合意义来考虑该递推式——
    从$n$个数中选$m$个,则考虑最后一个数是否被选,然后化为从$n−1$个数中选$m$个或$m−1$个。
    该方法复杂度为$O(n^2)$,但是简单自然,且对模数无特殊要求。

    模意义下的组合数计算

    对质数$p$取模的组合数,是常见的组合数求解形式。
    $C_n^m=frac{n!}{m!(n-m)!}$
    也就是我们只需要处理阶乘即可。
    则可以处理模意义下的阶乘,由于还需要除法,需要顺便处理$frac{1}{i!}$.
    求逆元可用快速幂解决。
    一般预处理只求$frac{1}{maxn!}$,然后根据$frac{1}{i!}=frac{1}{(i+1)!}(i+1)$倒推即可。
    相比刚才的方法, 这个方法对模数有要求,复杂度为$O(n log n)$.
    那$n$和$m$都更大的情况呢?

    组合数的计算方式

    若计算$C_n^mmod p$($p$为质数),则组合数满足以下公式:
    $$C_nmmod p=C_{frac{n}{p}}{frac{m}{p}}C_{n mod p}^{m mod p}$$
    这被称为卢卡斯定理
    运用该式可以进行递归计算,且只涉及$p$以内的组合数。

    而如果要计算$C_nmmod pk$($p$为质数),
    由$C_n^m=frac{n!}{m!(n-m)!}$,易知:
    我们不妨计算每个阶乘除去$p$的部分,这样分母也存在逆元。
    这可以分治解决,排除$p$后,$n!$被分为若干循环外加一段,每个循环长度为$n^k$。
    然后将含有$p$的部分除掉$p$化为子阶乘递归处理。
    最后用库默尔定理计算含有多少$p$,当然也可以在分治过程中直接统计。
    此方法适用于$p^k$不大的情况。
    可以看出该方法能结合中国剩余定理推广至对一般数$p$的组合数取模(要求分解出的$p^k$都不大)。
    (什么,你问我具体的公式?上面的库默尔定理有下划线发现了吗?我要是会公式我还只讲思路干嘛

    例题

    例1

    给你两个数$n$与$m$。
    统计有多少长度为$n$非负整数序列$A$,使元素和为$m$。
    答案对$1000000007$取模。

    $n,mleq1000000$

    思路:
    将$m$加上$n$,则转化为统计正整数序列。
    该问题可以这样描述:有$n$个盒子,将$m$个球放入其中使得每个盒子至少一个的方案数。
    我们将球排成一行,考虑将$n-1$个隔板插入其中,只能在球与球间插入,且至多插入一个。则与上述问题等价。
    从组合意义上可以得知结果为$C_{m-1}^{n-1}$.
    该结论也被称作抽屉原理

    例2

    在一个网格图中,从$(0,0)$走到$(n,m)$,要求不能超越$y=x$这条斜线,每次往右或往上走长度为$1$,求方案数。
    答案对$1000000007$取模。

    $n,mleq1000000$

    思路:
    (第一眼看上去是不是很像卡特兰数我刚开始也这么认为,但很抱歉,不保证$m=n$)
    直接计算走到$(n,m)$的方案数,可以知道一定有$n+m$步,其中有$n$步向右,$m$步向上。
    如何考虑$y=x$?
    不能超越$y=x$,即不能抵达$y=x+1$。
    对于每一条抵达了$y=x+1$的路径,找到最后一个交点,将之后的路径翻转,可以得到一条到$(n,m)$对称点的路径。
    而一条到$(n,m)$对称点的路径,必定与$y=x+1$相交,找到最后一个交点可翻转回去。因此从$(0,0)$到$(n,m)$关于$y=x+1$的对称点的路径数等于非法路径数。

    例3 CF785D Anton and School - 2

    一个长度为$n$的括号序列,问有多少非空子序列长度为偶数,且前一半为左括号后一半为右括号(例如“(())”)。
    答案对$1000000007$取模。

    $nleq200000$

    思路:
    可以枚举最后一个左括号,假如它左边有$A$个左括号,右边有$B$个右括号。
    贡献为$sum C_A{t-1}C_Bt$
    然而我们无法每次枚举计算该式子。
    考虑变化式子为$sum C_A{t-1}C_B{B-t}$
    可考虑该式子组合意义——
    有$A+B$个球排成一行,选择其中$B-1$个球的方案数。
    该求和式则是在枚举前$A$个球中选$t-1$个。
    即可以推广出结论——
    $$sum C_AiC_B{n-1}=C_{A+B}^n$$
    该式被称为范德蒙恒等式

    代码(贴一下本蒟蒻的代码):

    #include<bits/stdc++.h>
    #define N 400010
    #define MOD 1000000007
    
    using namespace std;
    
    int l,r,len;
    long long ans;
    long long fac[N],inv[N];
    string str;
    
    long long Pow(long long a,long long p) {
    	a%=MOD;
    	long long ans=1;
    	while(p) {
    		if(p&1) {
    			ans=ans*a%MOD;
    		}
    		a=a*a%MOD;
    		p>>=1;
    	}
    	return ans;
    }
    
    void Init() {
    	fac[0]=1;
    	inv[0]=1;
    	for(int i=1;i<N;i++) {
    		fac[i]=fac[i-1]*i%MOD;
            inv[i]=Pow(fac[i],MOD-2);
    	}
    	return;
    }
    
    long long Calc(int m,int n) {
    	if(m<n||m<0) {
    		return 0;
    	}
    	long long res=fac[m]*inv[m-n]%MOD*inv[n]%MOD;
    	return res;
    }
    
    int main()
    {
    	cin>>str;
    	len=str.length();
    	Init();
    	for(int i=0;i<len;i++) {
    		if(str[i]==')') {
    			r++;
    		}
    	}
    	for(int i=0;i<len;i++) {
    		if(str[i]==')') {
    			r--;
    		}
    		else {
    			ans=(ans+Calc(l+r,l+1))%MOD;
    			l++;
    		}
    	}
    	cout<<ans;
    	return 0;
    }
    

    小结

    组合数应用十分广泛,在信奥中常作为数论试水题出现,也经常出没在各大省选中。而在高数联中,更是作为入门第一讲(排列组合)。所以学好组合数是非常必要的。
    等我有时间再写一个奥数的组合数(乱立Flag)

  • 相关阅读:
    如何选择机器学习算法 转
    机器学习经典算法详解及Python实现--基于SMO的SVM分类器
    机器学习(Machine Learning)&深度学习(Deep Learning)资料
    计算智能在设备状态维护中的应用
    LaTeX 在编译时出现 File ended while scanning use of @writefile错误
    LaTeX 中插入图片使其紧跟插入的文字之后
    LaTeX 制作表格
    LaTeX 中换段落
    LaTeX 中使用三级标题
    使用 WinEdt 来写中文文章or 建模论文
  • 原文地址:https://www.cnblogs.com/luoshui-tianyi/p/11405522.html
Copyright © 2011-2022 走看看