组合计数
组合数
\(C^m_n\) = \(\dfrac{n (n-1) (n-2) \cdots (n-m+1)}{m!}\) = \(\dfrac{n!}{m! (n-m)!}\)
\(C^m_n\) = \(C^m_{n-1}\) +\(C^{m-1}_{n-1}\)
递推法求组合数
求组合数Ⅰ \(\quad\) \(C^m_n\) = \(C^m_{n-1}\) +\(C^{m-1}_{n-1}\)
//递推法求组合数模板
//c[a][b]存储C^a_b的方案数
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(j==0)c[i][j]=1;
else c[i][j]=c[i-1][j]+c[i-1][j-1];
预处理逆元求组合数
首先预处理出所有阶乘取模的余数 fact[N] , 以及所有阶乘取模的逆元 infact[N]
如果取模的数是质数, 可以用费马小定理求逆元
求组合数Ⅱ \(\quad\) \(C^m_n\) = \(\dfrac{n!}{m!(n-m)!}\)
//快速幂模板
int qmi(int a,int k,int p)
{
int res=1;
while(k)
{
if(k&1)res=(long long)res*a%p;
a=a*a%p;
k>>=1;
}
return res;
}
//预处理阶乘的余数和阶乘余数的逆元
fact[0]=infact[0]=1;
for(int i=1;i<N;i++)
{
fact[i]=(long long)fact[i-1]*i%mod;
infact[i]=(long long)infact[i-1]*qmi(i,mod-2,mod)%mod;
}
Lucas定理
Lucas定理: 若p是质数, 则对于任意整数 1 \(\le\) m \(\le\) n , 有:
\(C^m_n\) \(\equiv\) \(C^{m\%p}_{n\%p}\) \(\cdot\) \(C^{m/p}_{n/p}\) (mod p)
求组合数Ⅲ \(\quad\) \(C^m_n\) \(\equiv\) \(C^{m\%p}_{n\%p}\) \(\cdot\) \(C^{m/p}_{n/p}\) (mod p)
//快速幂模板
int qmi(int a,int k,int p)
{
int res=1;
while(k)
{
if(k&1)(long long)res=res*a%p;
a=(long long)a*a%p;
k>>=1;
}
return res;
}
//通过定理求组合数C^a_b
int C(int a,int b,int p)
{
if(a<b)return 0;
long long x=1,y=1; //x是分子,y是分母
for(int i=a,j=1;j<=b;i--,j++)
{
x=(long long)x*i%p;
y=(long long)y*j%p;
}
return x*(long long)qmi(y,p-2,p)%p;
}
int Lucas(long long a,long long b,int p)
{
if(a<p&&b<p)return C(a,b,p);
return (long long)C(a%p,b%p,p)*Lucas(a/p,b/p,p)%p;
}
分解质因数法求组合数
当我们需要求出组合数的真实值, 而非对某个数的余数时, 分解质因数的方式比较好用:
① 筛法求出范围内的所有质数
② 通过 \(C^b_a\) = \(\dfrac{a!}{b!(a-b)!}\) 这个公式求出每个质因子的次数
\(\quad\) n ! 中 p 的次数是 [ \(\dfrac{n}{p}\) ] + [ \(\dfrac{n}{p^2}\) ] + \(\cdots\)
③ 用高精度乘法将所有质因子相乘
int primes[N],cnt; //存储所有质数
int sum[N]; //存储每个质数的次数
bool st[N]; //存储每个数是否已被筛掉
void get_primes(int n) //线性筛法求素数
{
for(int i=2;i<=n;i++)
{
if(!st[i])primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++)
{
st[primes[j]*i]=true;
if(i%primes[j]==0)break;
}
}
}
int get(int n,int p) //求n!中p的次数
{
int res=0;
while(n)
{
res+=n/p;
n/=p;
}
return res;
}
vector<int> mul (vector<int>a, int b) //高精度乘低精度模板
{
vector<int>c;
int t=0;
for(int i=0;i<a.size();i++)
{
t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
while(t)
{
c.push_back(t%10);
t/=10;
}
return c;
}
get_primes(a); //预处理范围内的所有质数
for(int i=0;i<cnt;i++) //求每个质因数的次数
{
int p=primes[i];
sum[i]=get(a,p)-get(b,p)-get(a-b,p);
}
vector<int>res;
res.push_back(1);
for(int i=0;i<cnt;i++) //用高精度乘法将所有质因子相乘
for(int j=0;j<sum[i];j++)
res=mul(res,primes[i]);
卡特兰数
给定n个0和n个1, 它们按照某种顺序排成长度为2n的序列, 满足任意前缀中0的个数都不少于1的个数的序列的数量为:
Cat (n) = \(C^n_{2n}\) - \(C^{n-1}_{2n}\) = \(\dfrac{C^n_{2n}}{n+1}\)