求组合数常用的方法

求C(n,m) //其中n>=m

一、暴力

 1.按定义直接计算:

n,m<15

long long的大小制约着n,m,如果加上mod,n,m可以再高些。

ll C(int n,int m)     //暴力
{
	if(n>m) return 0;
	if(n<m-n) n=m-n;
	ll ans=1;
	for(int i=n+1;i<=m;i++) ans*=i;
	for(int i=1;i<=m-n;i++) ans/=i;
	return ans;
}

2.二维数组打表:

n,m<5000

通过递推公式实现:com[i][j]=com[i-1][j]+com[i-1][j-1];

#define ll long long
#define M 5000
ll com[M][M];
void Prons()
{
    com[0][1]=1;
    for(int i=1;i<M;i++)     //i>j
        for(int j=0;j<=i;j++){
            if(j==0) com[i][j]=1;
            else com[i][j]=com[i-1][j]+com[i-1][j-1];
        }
}

特点:优点是简单短,缺点是时间慢,范围小

二、利用快速幂

3.快速幂+逆元

n,m<1e6

仍然是利用 C(n,m)=n!/[m!*(n-m)!] 的计算,预处理阶乘数组降低时间,qpow是逆元

ll fac[1000005];        //阶乘
void init(){
    fac[0]=1;
    for(int i=1;i<1e6;i++)
        fac[i]=(fac[i-1]*i)%mod;
}
ll qpow(ll a,ll b)
{
    ll ans=1;
    a%=mod;
    while(b){
        if(b&1){
            ans=ans*a%mod;
            b--;
        }
        b>>= 1;
        a=a*a%mod;
    }
    return ans;
}
ll C(int n,int m){
    return fac[n]*qpow(fac[n-m],mod-2)%mod*qpow(fac[m],mod-2)%mod;
}

特点:时间复杂度是log2,如果符合范围,这个是时间最快的。这个也是最好用的

4.卢斯卡定理1:

通过卢思卡公式实现

p为质数 n≤1e18,m≤1e6,mod≤1e9(可以是1e9+7)

#define ll long long
#define M 100005
const ll mod=10007;
ll a,b;
ll qpow(ll a,ll b)
{
    ll ans=1;
    a%=mod;
    while(b)
    {
        if(b&1)
        {
            ans=ans*a%mod;
            b--;
        }
        b>>= 1;
        a=a*a%mod;
    }
    return ans;
}
ll C(ll n,ll m)
{
    if(m>n) return 0;
    ll ans=1;
    for(int i=1;i<=m;i++)
    {
        ll a=(n+i-m)%mod;
        ll b=i%mod;
        ans=ans*(a*qpow(b,mod-2)%mod)%mod;
    }
    return ans;
}
ll Lucas(ll n,ll m)    //n>m
{
    if(m==0) return 1;
    return C(n%mod,m%mod)*Lucas(n/mod,m/mod)%mod;
}

特点:这个比下面的常用些,mod可以取到1e9+7。

n,m比较大的情况下可以用,如果n,m都<1e6,还是建议用上的面,因为可能会超时。

5.卢斯卡定理2:

 m≤n≤1e18,mod≤1e6

#define ll long long
#define M 1000005
ll a,b,mod;
ll f[M];
void init(){
    f[0]=1;
    for(int i=1;i<=mod;++i)
        f[i]=f[i-1]*i%mod;
}
ll qpow(ll a, ll b){
    ll ans=1;
    a%=mod;
    while(b)
    {
        if(b&1)
        {
            ans=ans*a%mod;
            b--;
        }
        b>>= 1;
        a=a*a%mod;
    }
    return ans;
}
ll Lucas(ll n,ll k){
    ll ret=1;
    while(n&&k){
        ll nn=n%mod,kk=k%mod;
        if(nn<kk) return 0;
        ret=ret*f[nn]*qpow(f[kk]*f[nn-kk]%mod,mod-2)%mod;
        n/=mod;
        k/=mod;
    }
    return ret;
}

特点:卢斯卡定理适用于n,m超大的情况,但是时间复杂度并不是很好,有两种可供选择。

具体的知识没有相关了解,建议自己去搜搜,本文只做适用范围总结

猜你喜欢

转载自blog.csdn.net/m0_58177653/article/details/125127406
今日推荐