求组合数(三种方法)

求组合数I(暴力做法) O ( n ) O(n) O(n)

公 式 : C a b = C a − 1 b − 1 + C a − 1 b 公式:C_{a}^{b}=C_{a-1}^{b-1}+C_{a-1}^{b} Cab=Ca1b1+Ca1b

做法类似杨辉三角
模板题:AcWing 885. 求组合数 I
注意:当b等于0或者a==b的时候直接赋值为1
代码如下:

#include<bits/stdc++.h>
using namespace std;

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

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

int main()
{
    for(int i=1;i<MAXN;++i)
        for(int j=0;j<=i;++j)
        {
            if(j==0||j==i)
                arr[i][j]=1;
            else
                arr[i][j]=(arr[i-1][j-1]+arr[i-1][j])%mod;
        }
        
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d",&a,&b);
        printf("%d\n",arr[a][b]);
    }
    
    return 0;
}

求组合数II(逆元, O ( n l o g n ) O(nlogn) O(nlogn)

C a b = a ! ( a − b ) ! b ! C_{a}^{b}=\frac{a!}{(a-b)!b!} Cab=(ab)!b!a!
f a c t [ a ] fact[a] fact[a]数组存储   a \,a a的阶乘   a ! \,a! a! i n f a c t [ a ] infact[a] infact[a]存储   a ! \,a! a!的逆元,用快速幂来求
那么 C a b = f a c t [ a ]     ∗     i n f a c t [ a − b ] % m o d     ∗     i n f a c t [ b ] % m o d C_{a}^{b}=fact[a]\,\,\,*\,\,\,infact[a-b]\%mod\,\,\,*\,\,\,infact[b]\%mod Cab=fact[a]infact[ab]%modinfact[b]%mod
需要注意的是     i n f a c t [ i ] = i n f a c t [ i − 1 ]     ∗     q m i ( i , m o d − 2 , m o d ) % m o d \,\,\,infact[i] = infact[i - 1]\,\,\, *\,\,\, qmi(i, mod - 2, mod) \% mod infact[i]=infact[i1]qmi(i,mod2,mod)%mod
前面是 1 ( i − 1 ) ! \frac{1}{(i-1)!} (i1)!1,后面是 1 i \frac{1}{i} i1,乘起来就是 1 i ! \frac{1}{i!} i!1

模板题:AcWing 886. 求组合数 II
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN=1e5+10,mod=1e9+7;

ll fact[MAXN],infact[MAXN],n,a,b;

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

int main()
{
    fact[0]=infact[0]=1;
    for(int i=1;i<MAXN;++i)
    {
        fact[i]=fact[i-1]*i%mod;
        infact[i]=infact[i-1]*qmi(i,mod-2,mod)%mod;
    }
    
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d",&a,&b);
        printf("%d\n",fact[a]*infact[a-b]%mod*infact[b]%mod);
    }
    
    return 0;
}

求组合数III(卢卡斯定理)

C a b = C a    m o d   p b    m o d   p + C a / p b / p     ( m o d   p ) , a / p 和 b / p 表 示 整 除 , 证 明 有 点 复 杂 , 就 不 放 在 这 里 了 C_{a}^{b}=C_{a\,\,mod\,p}^{b\,\,mod\,p}+C_{a/p}^{b/p}\,\,\,(mod\,p),a/p和b/p表示整除,证明有点复杂,就不放在这里了 Cab=Camodpbmodp+Ca/pb/p(modp)a/pb/p
模板题:AcWing 887. 求组合数 III
注意传进 l u c a s lucas lucas 里面需要是 l l ll ll 类型
代码如下:

#include<iostream>

using namespace std;
#define ll long long


int p;

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


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

ll lucas(ll a, ll b)
{
    if(a < p && b < p)
        return C(a, b);

    return C(a % p, b % p) * lucas(a / p, b / p) % p;
}

int main()
{
    int n;
    scanf("%d", &n);
    while(n--)
    {
        ll a, b;
        scanf("%lld%lld%lld", &a, &b, &p);
        printf("%lld\n", lucas(a, b));
    }
    return 0;
}

求组合数IV

输入 a , b a,b a,b,求 C a b C_{a}^{b} Cab 的值,不模,用高精度
1 ≤ b ≤ a ≤ 5000 1≤b≤a≤5000 1ba5000

C a b = a ! ( a − b ) ! b ! = p 1 a 1 ∗ p 2 a 2 ∗ ⋅ ⋅ ⋅ ∗ p k a k ← 分 解 质 因 数 C_{a}^{b}=\frac{a!}{(a-b)!b!}=p_1^{a1}*p_2^{a2}*···*p_k^{a_k}←分解质因数 Cab=(ab)!b!a!=p1a1p2a2pkak
把分子中   a ! \,a! a! 里面的含有   p \,p p的个数求出来,再求出分子的,再相减
s u m [ i ] 表 示   a   的 阶 乘 里 面 有 多 少 个 第 i 个 质 数 , 质 数 从 2 开 始 算 sum[i]表示\,a\,的阶乘里面有多少个第i个质数,质数从2开始算 sum[i]ai2
s u m [ i ] = a p + a p 2 + a p 3 + ⋅ ⋅ ⋅ sum[i]=\frac{a}{p}+\frac{a}{p^2}+\frac{a}{p^3}+··· sum[i]=pa+p2a+p3a+
比如 8 ! = 1 ∗ 2 ∗ 3 ∗ 4 ∗ 5 ∗ 6 ∗ 7 ∗ 8 8!=1*2*3*4*5*6*7*8 8!=12345678
s u m [ 1 ] = 8 / 2 + 8 / 4 + 8 / 8 = 4 + 2 + 1 = 7 , 表 示 第 一 个 质 数 2 在 8 的 阶 乘 里 面 有 7 个 , 第 二 个 质 数 是 3 ⋅ ⋅ ⋅ 等 等 sum[1]=8/2+8/4+8/8=4+2+1=7,表示第一个质数2在8的阶乘里面有7个,第二个质数是3···等等 sum[1]=8/2+8/4+8/8=4+2+1=72873

模板题如下:AcWing 888. 求组合数 IV
步骤如下:
①:先线性筛预处理出来小于a的所有质数

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;
        }
    }
}

②:遍历得到组合数的结果里面所有质数的个数, g e t get get 函数也要注意有技巧,注意 s u m [ i ] sum[i] sum[i] 表示第 i i i 个质数的个数

int get(int a, int p)
{
    int res = 0;
    while(a)
    {
        res += a / p;
        a /= p;
    }

    return res;
}
int main()
{
	for(int j = 0; j < cnt; ++j)
	{
	    int p = primes[j];//这里相当于离散化了
	    sum[j] = get(a, p) - get(a - b, p) - get(b, p);
	}
}

③:对于结果 v e c t o r < i n t > r e s vector<int>res vector<int>res,首先先 p u s h _ b a c k push\_back push_back一个 1 1 1,然后在分别处理每个质数的与它的个数,这里是大数相乘,最后倒序输出

完整代码如下:

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 5010;
int primes[MAXN], st[MAXN], cnt, sum[MAXN];

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 a, int 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(); ++i)
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while(t)
    {
        c.push_back(t % 10);
        t /= 10;
    }
    return c;
}

int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    get_primes(a);

    for(int j = 0; j < cnt; ++j)
    {
        int p = primes[j];//这里相当于离散化了
        sum[j] = get(a, p) - get(a - b, p) - get(b, p);
    }

    vector<int> res;
    res.push_back(1);
    for(int j = 0; j < cnt; ++j)
        for(int i = 1; i <= sum[j]; ++i)
            res = mul(res, primes[j]);

    for(int i = res.size() - 1; i >= 0; --i)
        printf("%d", res[i]);

    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值