ACwing算法备战蓝桥杯——Day24——组合数

思路:

求组合数有两种算法,根据预处理的数据量大小来判断用哪个。

两者的思想都是先进行预处理出一个表,每次询问都通查找表中处理好的数据来获得答案。

当预处理数据量较小时:

主要内容

数据类型

作用

二维数组c[N][N]

int

c[a][b]表示在a里选b个数的方案个数

思路:

采用递推的方法:c[a][b]=c[a-1][b]+c[a-1][b];

解释:

有点类似于DP:假设有a个苹果,要从中选出b个苹果。假设a中最后一个苹果为k,在所有选法中,只有选k的和不选 k的两种情况,分别对应c[a-1][b-1]与c[a-1][b]两种情况,也就有c[a][b]=c[a-1][b-1]+c[a-1][b];

模板题:

给定 n 组询问,每组询问给定两个整数 a,b,请你输出 C(b,a)mod(109+7) 的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组 a 和 b。

输出格式
共 n 行,每行输出一个询问的解。

数据范围
1≤n≤10000,
1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1

代码:
#include <iostream>
 
using namespace std;
 
const int N=2010,mod=1e9+7;

int c[N][N];

void init(){
    for(int i=0;i<N;i++){
        for(int j=0;j<=i;j++){//因为j初始为0所以i小于0的时候就不会发生进入循环,也就不用担心i-1越界
            if(!j) c[i][j]=1;
            else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }
    }
}


int main(){
    
    init();
    
    int n;
    
    scanf("%d",&n);
    
    for(int i=1;i<=n;i++){
        int a,b;
        
        scanf("%d%d",&a,&b);
        
        printf("%d\n",c[a][b]);
    }
    
    return 0;
}

当预处理数据量较大时:

思路:

主要内容

数据类型

作用

fact[]

int

fact[i]储存i的阶乘

infact[]

int

infact[i]储存i的阶乘的逆元

设C(b,a)为在a个数里选b个数的方案数,现要求C(b,a).(如果像方法1一样处理,会超时)

方法2:

C(b,a)=a!/((a-b)!*b!);

求一遍数的阶乘,然后带入上述公式得到答案;

这里有一个问题:就是我们在预处理fact[]时会进行mod处理防止数据过大(因为是阶乘会很大),

但是(n/m)mod k !=(n mod k)/(m mod k);

所以直接将阶乘带入公式会导致答案出错。

根据a/b同余于a*(b的逆元),所以公式a!/((a-b)!*b!)==a!*((a-b)!)的逆元*(b!)的逆元(逆元在快速幂里讲过).

所以要再预处理一个逆元数组infact[];

模板题:

给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组 a 和 b。

输出格式
共 n 行,每行输出一个询问的解。

数据范围
1≤n≤10000,
1≤b≤a≤105
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1

代码:
//求组合数的两种方法都是进行预处理,不在询问的基础上拓深时间复杂度
#include <iostream>
using namespace std;

typedef long long LL;

const int N=1e5+10,mod=1e9+7;//mod为质数

LL fact[N],infact[N];//fact表示阶乘,infact表示阶乘的逆元

int qmi(int a,int b,int m){//快速幂,时间复杂度log级

    LL res=1;
    
    while(b){
        
        if(b&1) res=res*a%m;
        
        a=(LL)a*a%m;
        
        b>>=1;
    }
    
    return res;
}

int main(){
    
    //预处理
    fact[0]=infact[0]=1;
    for(int i=1;i<N;i++){
        fact[i]=fact[i-1]*i%mod;//暂时改变数据类型
    }
    
    int n;
    scanf("%d",&n);
    
    while(n--){
        int a,b;
        scanf("%d%d",&a,&b);
        
        printf("%d\n",fact[a]/(fact[a-b]*fact[b]));//三个1e9相乘会爆LL所以要及时模,模两个
    }
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

切勿踌躇不前

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值