组合数
Posted luoshui-tianyi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了组合数相关的知识,希望对你有一定的参考价值。
组合数
其实我也没想到我第一篇博客会讲一个偏数学的内容。
主要是我太弱了,只会这个
何为组合数
记号\(C_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_n^i=C_n^n-i\)
\(C_n^m=0(m>n)\)
\(\sum_i=1^nC_n^i=2^n\)
组合数的计算方法
根据组合数的定义,我们可以得到计算公式。
\[C_n^m=\fracn(n-1)\cdots(n-m+1)m!=\fracn!m!(n-m)!\]
即统计有序选择的方案数\(n(n-1)\cdots(n-m+1)\),然后除掉每个方案被重复计数的\(m!\)。
有了这个式子后,我们只要处理出阶乘及其倒数,便可以计算组合数。
由于组合数一般很大,实际上大多时候在模意义下计算。
组合数还存在递推式。
即\[C_n^m=C_n-1^m-1+C_n-1^m\]
从组合意义来考虑该递推式——
从\(n\)个数中选\(m\)个,则考虑最后一个数是否被选,然后化为从\(n?1\)个数中选\(m\)个或\(m?1\)个。
该方法复杂度为\(O(n^2)\),但是简单自然,且对模数无特殊要求。
模意义下的组合数计算
对质数\(p\)取模的组合数,是常见的组合数求解形式。
\(C_n^m=\fracn!m!(n-m)!\)
也就是我们只需要处理阶乘即可。
则可以处理模意义下的阶乘,由于还需要除法,需要顺便处理\(\frac1i!\).
求逆元可用快速幂解决。
一般预处理只求\(\frac1maxn!\),然后根据\(\frac1i!=\frac1(i+1)!(i+1)\)倒推即可。
相比刚才的方法, 这个方法对模数有要求,复杂度为\(O(n\ log\ n)\).
那\(n\)和\(m\)都更大的情况呢?
组合数的计算方式
若计算\(C_n^mmod\ p\)(\(p\)为质数),则组合数满足以下公式:
\[C_n^mmod\ p=C_\fracnp^\fracmpC_n\ mod\ p^m\ mod\ p\]
这被称为卢卡斯定理。
运用该式可以进行递归计算,且只涉及\(p\)以内的组合数。
而如果要计算\(C_n^mmod\ p^k\)(\(p\)为质数),
由\(C_n^m=\fracn!m!(n-m)!\),易知:
我们不妨计算每个阶乘除去\(p\)的部分,这样分母也存在逆元。
这可以分治解决,排除\(p\)后,\(n!\)被分为若干循环外加一段,每个循环长度为\(n^k\)。
然后将含有\(p\)的部分除掉\(p\)化为子阶乘递归处理。
最后用库默尔定理计算含有多少\(p\),当然也可以在分治过程中直接统计。
此方法适用于\(p^k\)不大的情况。
可以看出该方法能结合中国剩余定理推广至对一般数\(p\)的组合数取模(要求分解出的\(p^k\)都不大)。
(什么,你问我具体的公式?上面的库默尔定理有下划线发现了吗?我要是会公式我还只讲思路干嘛)
例题
例1
给你两个数\(n\)与\(m\)。
统计有多少长度为\(n\)非负整数序列\(A\),使元素和为\(m\)。
答案对\(1000000007\)取模。
\(n,m\leq1000000\)
思路:
将\(m\)加上\(n\),则转化为统计正整数序列。
该问题可以这样描述:有\(n\)个盒子,将\(m\)个球放入其中使得每个盒子至少一个的方案数。
我们将球排成一行,考虑将\(n-1\)个隔板插入其中,只能在球与球间插入,且至多插入一个。则与上述问题等价。
从组合意义上可以得知结果为\(C_m-1^n-1\).
该结论也被称作抽屉原理。
例2
在一个网格图中,从\((0,0)\)走到\((n,m)\),要求不能超越\(y=x\)这条斜线,每次往右或往上走长度为\(1\),求方案数。
答案对\(1000000007\)取模。
\(n,m\leq1000000\)
思路:
(第一眼看上去是不是很像卡特兰数,我刚开始也这么认为,但很抱歉,不保证\(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\)取模。
\(n\leq200000\)
思路:
可以枚举最后一个左括号,假如它左边有\(A\)个左括号,右边有\(B\)个右括号。
贡献为\(\sum C_A^t-1C_B^t\)
然而我们无法每次枚举计算该式子。
考虑变化式子为\(\sum C_A^t-1C_B^B-t\)
可考虑该式子组合意义——
有\(A+B\)个球排成一行,选择其中\(B-1\)个球的方案数。
该求和式则是在枚举前\(A\)个球中选\(t-1\)个。
即可以推广出结论——
\[\sum C_A^iC_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)
以上是关于组合数的主要内容,如果未能解决你的问题,请参考以下文章