【学习笔记】处理极大组合数

Cnm

下面是对四个不同数量级的处理

一、1 <= n , m <= 20

可以直接暴力阶乘求解

ll gcd(ll m,ll n){     //计算最大公约数
    return n ? gcd(n,m%n) : m;
}

ll f(ll n)
{
    ll ret=1;
    for(ll i=1;i<=n;i++)    ret*=i;
    return ret;
}

ll C(ll n,ll m)    //计算组合数
{
    ll fz=f(n);    //分子
    ll fm=f(m)*f(n-m);    //分母
    ll z=gcd(fz,fm);    //约分
    ll ans=(fz/z)/(fm/z);
}

不难看出,里面有很多重复计算的值,比如 m! 。
所以我们还可以小小的优化一下。
优化点:
①根据组合数(Cnm)的性质,当 m > n/2 时,效果与 n-m 一致。
②分子、分母去重。
分子:n * (n-1) * (n-2) …… 3 * 2 * 1
分母:[ m * (m-1) … 2 * 1 ] * [ (n-m) * (n-m-1) … 2 * 1 ]
加粗表示可以约分
③在计算的过程中,也可以不断地除去能除的数。比如 2 3 ……

long long cnm(int n,int m)
{
    long long s = 1;
    int k = 1;
    if(m > n/2)
        m = n-m;
    for(int i=n-m+1;i<=n;i++)
    {
        s *= (long long)i;
        while(k<=m && s%k == 0)
        {
            s /= (long long)k;
            k++;
        }
    }
    return s;
}

【注意】仍只能计算小范围!

二、数量级:103

由高中数学知识可以知道,组合数实际上就是杨辉三角,那我们就可以用递推式。

#include <bits/stdc++.h>
#define mod 1000000007
using namespace std;
typedef long long ll;
const ll N=2005;
ll C[N][N];

void init()
{
    for(ll i=0;i<N;i++)
        for(ll j=0;j<=i;j++)
            if(!j)  C[i][j]=1;
            else    C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}

int main()
{
    init();
    ll a,b;
    cin>>a>>b;
    cout<<C[a][b]<<endl;
    return 0;
}

三、数量级:10^5

预处理阶乘 + 逆元

数量级已经超过 long long 的范围了,而且分数不能直接取模,所以我们采用逆元处理。
先用数组存储每一个的阶乘结果和逆元结果。

不开 infac[] 数组也可以,直接求就是了。(见下面被注释掉的代码)

逆元不会点这里

原题链接

AC代码
#include <bits/stdc++.h>
#include <iostream>
#define mod 1000000007 
using namespace std;
typedef long long ll;
const ll N=1e5+5;
ll fac[N],infac[N];
 
ll fastpower(ll base,ll power)
{
    ll ret=1;
    while(power){
        if(power&1)    ret=ret*base%mod;
        power>>=1;
        base=base*base%mod;
    }
    return ret;
}
 
void init()
{
    fac[0]=infac[0]=1;
    for(ll i=1;i<N;i++){
        fac[i]=fac[i-1]*i%mod;    //阶乘
        infac[i]=infac[i-1]*fastpower(i,mod-2)%mod;    //逆元
        //不想要 infac[] 可以把这行删掉
    }
}
 
ll Cc(ll n,ll m){    //组合数
    return fac[n]*infac[m]%mod*infac[n-m]%mod;
}

//ll Cc(ll n,ll m)    //组合数{
//    return fac[n]*fastpower(fac[m]*fac[n-m]%mod,mod-2)%mod;
//}
 
int main()
{
    init();
    int t;
    scanf("%d",&t);
    while(t--){
        ll n,m,k,q;
        scanf("%lld%lld%lld%lld",&n,&m,&k,&q);
        if(k>n)    printf("0\n");
        else{
            ll fz=Cc(n,k);
            ll fm=Cc(n+m,k);
            fz=fastpower(fz,q)%mod;
            fm=fastpower(fm,q)%mod;
            ll ans=(fz*fastpower(fm,mod-2))%mod;
            printf("%lld\n",ans);   
        }
    }
    return 0;
}

四、数量级:10^18

这种数量级只能用卢卡斯定理

【证明】
一位大佬的证明,很简洁
也是大佬的,可以与上面补充使用

但适合测试组数较少的,否则容易超出题目的内存限制

#include <bits/stdc++.h>
#define mod 1000000007
using namespace std;
typedef long long ll;

ll fastpower(ll base,ll power)
{
    ll ret=1;
    while(power){
        if(power&1) ret=ret*base%mod;
        power>>=1;
        base=base*base%mod;
    }
    return ret;
}

ll C(ll a,ll b)
{
    if(b>a) return 0;
    ll res=1;
    for(ll i=1,j=a;i<=b;i++,j--){
        res=res*j%mod;
        res=res*fastpower(i,mod-2)%mod;
    }
    return res;
}

ll Lucas(ll a,ll b)
{
    return C(a%mod,b%mod)*Lucas(a/mod,b/mod)%mod;
}

void sovle()
{
    ll n,m,k,q;
    cin>>n>>m>>k>>q;
    ll fz=Lucas(n,k);
    ll fm=Lucas(n+m,k);
    fz=fastpower(fz,q);
    fm=fastpower(fm,q);
    ll ans=(fz*fastpower(fm,mod-2)%mod)%mod;
    cout<<ans<<endl;
}

int main()
{
    int n;
    scanf("%d",&n);
    while(n--)  solve();
    return 0;
}

欢迎指正修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值