ch3(数学)——组合数学(Lucas定理、Catalan数)

1.自然语言描述
组合数的计算是高中数学的知识;计算机算组合数的方法不唯一,可以按照组合数原始式去算,也可以用定理来递归计算。具体怎么算,要看问题数据规模。

组合数原始式:C(n,m)=n!/(m!*(n-m)!);
有一重要性质:C(n,m)=C(n-1,m)+C(n-1,m-1);(解释:对于n个元素中的某个元素,C(n,m)包含两种情况;第一种是选取了这个元素,也就是说选的m个元素中包含这个元素,去掉这个元素会使m个也去掉1个,情况数为C(n-1,m-1);第二种是未选取这个元素,m个中不包含这个元素,情况数为C(n-1,m)

当组合数较大时,预处理出数据范围内的阶乘和阶乘模p的乘法逆元(根据费马小定理使用快速幂计算)对于每个组合数计算可以直接给出答案。

Lucas定理:组合数较大时算模p结果(p必须是素数)有公式
C(n,m)≡C(n mod p,m mod p)*C(n/p,m/p) (mod p)
用它递归求解组合数,但由此可见,p也不能太大,一般在1e5左右。

对于直接计算组合数而不进行模运算的情况;可以通过将阶乘分解质因数来把计算阶乘转换为统计素数的指数部分,相比直接算阶乘减少计算次数,简化了计算。对于某个素数p,阶乘a!质因数分解结果中p^α,有α= a/p + a/p^2 + a/p^3 + … + a/p^k;据此,先用线性质数筛把数据范围内的质数筛出来,直接计算阶乘转换为记录阶乘分解质因数结果。

Catalan数:栈中序列的出入栈问题,路径记数问题呈现的数列都是Catalan数数列;计算公式为Catalan(n)=C(n,2n)/(n+1)。详细解释请点这儿!

2.代码描述
题目:Acwing.885 求组合数I题目链接

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN=2010,MOD=1e9+7;

int n,a,b,c[MAXN][MAXN];

void init()
{
    for(int i=0;i<MAXN;i++)
        for(int j=0;j<=i;j++)
            if(!j)
                c[i][j]=1;//c(0,i)=1
            else
                c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD;//c(b,a)=c(b-1,a-1)+c(b,a-1)
}

int main(void)
{
    init();//预处理出所有数据范围内的组合数
    
    cin>>n;
    
    while(n--){
        cin>>a>>b;
        cout<<c[a][b]<<endl;
    }
    
    return 0;
}

题目:Acwing.886 求组合数II题目链接

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN=1e5+10,MOD=1e9+7;

int fact[MAXN],infact[MAXN];//fact为阶乘,infact为阶乘的mod MOD逆元

LL qmi(int a,int k,int p)
{
    LL ans=1;
    while(k){
        if(k&1)
            ans=(LL)ans*a%MOD;
        k>>=1;
        a=(LL)a*a%MOD;
    }
    return ans;
}

int main(void)
{
    int n;
    cin>>n;
    
    fact[0]=infact[0]=1;
    for(int i=1;i<MAXN;i++){
        fact[i]=(LL)fact[i-1]*i%MOD;
        infact[i]=(LL)infact[i-1]*qmi(i,MOD-2,MOD)%MOD;//根据费马小定理用快速幂计算乘法逆元
    }
    
    while(n--){
        int a,b;
        cin>>a>>b;
        cout<<(LL)fact[a]*infact[b]%MOD*infact[a-b]%MOD<<endl;;
    }
    
    return 0;
}

题目:Acwing.887 求组合数III题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN=20;
LL p;

LL qmi(int a,int k)
{
    LL res=1;
    while(k){
        if(k&1)
            res=(LL)res*a%p;
        k>>=1;
        a=(LL)a*a%p;
    }
    return res;
}

LL C(int a,int b)
{
    LL res=1;
    for(int i=1,j=a;i<=b;i++,j--){
        res=(LL)res*j%p;
        res=(LL)res*qmi(i,p-2)%p;
    }
    return res;
}

LL lucas(LL a,LL b,LL p)//lucas定理:C(a,b)=C(a%p,b%p)*C(a/p,b/p)%p
{
    if(a<p&&b<p)//a与b均小于模数,即a/p=b/p=0,这里是递归边界,直接返回C(a,b) 
        return C(a,b)%p;
    else
        return C(a%p,b%p)*lucas(a/p,b/p,p)%p;//每次直接算出容易算的较小组合数,递归求解较大组合数
}

int main(void)
{
    int n;
    cin>>n;
    
    while(n--){
        LL a,b;
        cin>>a>>b>>p;
        cout<<lucas(a,b,p)<<endl;
    }
    
    return 0;
}

题目:Acwing.888 求组合数IV题目链接

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;
typedef long long LL;

const int MAXN=5050;
int primes[MAXN],cnt,sum[MAXN];
bool st[MAXN];
int a,b;

void init_primes(int a)//线性素数筛
{
    for(int i=2;i<=a;i++){
        if(!st[i])
            primes[cnt++]=i;
            
        for(int j=0;primes[j]<=a/i;j++){
            st[primes[j]*i]=true;
            if(i%primes[j]==0)
                break;
        }
    }
}

int calc(int a,int p)//统计数a质因数分解后含有质因子p的次数
{
    int res=0;
    while(a){
        res+=a/p;
        a/=p;
    }
    return res;
}

vector<int> mul(vector<int> a,int b)//高精度乘法模板
{
    vector<int> c;
    int t=0;
    for(int i=0;i<a.size()||t;i++){
        if(i<a.size())
            t+=a[i]*b;
        c.push_back(t%10);
        t/=10;
    }
    
    while(c.size()>1&&c.back()==0)
        c.pop_back();
    
    return c;
}

int main(void)
{
    cin>>a>>b;
    
    init_primes(a);//预处理,筛出1~a所有的质数
    
    for(int i=0;i<cnt;i++){
        int p=primes[i];
        sum[i]=calc(a,p)-calc(b,p)-calc(a-b,p);
    }//统计a!/(b!*(a-b)!)中所有质数的指数部分
    
    vector<int> ans;
    ans.push_back(1);//最终的结果非常大,适合用高精度乘法计算
    
    for(int i=0;i<cnt;i++)
        for(int j=0;j<sum[i];j++)//质数的sum[i]次方
            ans=mul(ans,primes[i]);
            
    for(int i=ans.size()-1;i>=0;i--)
        cout<<ans[i];
    cout<<endl;
    
    return 0;
}

题目:Acwing.889 满足条件的01序列题目链接

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long LL;
const int MOD=1e9+7;

int n;

LL qmi(LL a,LL k,int p)
{
    LL res=1;
    while(k){
        if(k&1)
            res=(LL)res*a%p;
        k>>=1;
        a=(LL)a*a%p;
    }
    return res;
}

int main(void)
{
    cin>>n;
    
    LL res=1,a=2*n,b=n;//Catalan数计算公式:C(2n,n)/(n+1)
    for(int i=a;i>a-b;i--)
        res=(LL)res*i%MOD;
    for(int i=1;i<=b;i++)
        res=(LL)res*qmi(i,MOD-2,MOD)%MOD;//将除法转换为模p逆元
        
    res=(LL)res*qmi(n+1,MOD-2,MOD)%MOD;
    
    cout<<res<<endl;
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值