组合数的研究

介绍

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

定义

组合数学里面有两个大的块。
一个是排列,一个是组合。
所谓排列就是在一个集合中取出的有序子集。
所谓组合就是在一个集合中取出的无序子集。
定义可能看不懂,但举个例子就很简单了。
比如有n个人,我们从中取出m个人。
把他们排成队,就是排列了。如果不关心顺序,只关心取出哪些人就是组合了。
下面介绍排列组合的几个基本定理和公式。
国际上一般记组合数为$\binom{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 } \times C_{n/p}^{m/p} \]
注意,这里p必须是质数。
公式不长记住就行,懒得证明。

5.组合数的和

\[\sum{C_n^i}(0\le i\le 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] \times B[m] \times B[n-m]\]
我们只要线性预处理出阶乘数组和逆元前缀积数组就可以了。
先求出1到n的所有数在模p意义下的逆元。
\[inv[i]=(p-\frac{p}{i})\times inv[p\%i]\%p\]
\[B[i+1]=B[i]\times inv[i+1]%p\]
\[A[i+1]=A[i]\times (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;
}
拓展卢卡斯定理

留坑待填
未完待续...

转载于:https://www.cnblogs.com/onglublog/p/9883154.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值