组合数学模板题

求组合数 I

题目链接

给定n组询问,每组询问给定两个整数a,b,请你输出$C_a^b$ mod (1e9+7)的值。

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

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

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

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

分析:
这道题的数据范围较小,而且是多组输入,可以打表,将所有 C a b C_a^b Cab都预处理出来,处理过程运用求组合数的递推公式 C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} Cab=Ca1b+Ca1b1,代码较为容易实现。
代码如下

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=2e3+10;
const int mod=1e9+7;
int q[N][N];
void init()
{
    for(int i=1;i<N;i++)
        for(int j=0;j<=i;j++)
            if(!j||j==i) q[i][j]=1;
            else q[i][j]=(q[i-1][j]+q[i-1][j-1])%mod;
}
int main()
{
    int n;
    scanf("%d",&n);
    init();
    while(n--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",q[a][b]);
    }
    return 0;
}

求组合数 II

题目链接

给定n组询问,每组询问给定两个整数a,b,请你输出$C_a^b$ mod (1e9+7)的值。

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

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

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

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

分析:
这道题的数据范围较大,不能够像组合数I打表预先处理出所有组合数的值,不过根据组合数公式 C a b = a ! ( a − b ) ! ∗ b ! C_a^b=\frac{a!}{(a-b)!*b!} Cab=(ab)!b!a!我们可以将其阶乘预处理出来,由于要 m o d   1 e 9 + 7 mod\ 1e9+7 mod 1e9+7,并且需要除法,这里需要求逆元,将除法转化为乘法。这样就能够解出来了。递推求出前缀积和逆元,逆元根据 ( a ∗ b ) − 1 = a − 1 ∗ b − 1 (a*b)^{-1}=a^{-1}*b^{-1} ab1=a1b1来求
代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int mod=1e9+7;
int q[N],f[N];
int qmi(int a,int b)//费马小定理,快速幂
{
    int res=1;
    while(b)
    {
        if(b&1)
            res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
    return res;
}
void init()
{
    q[0]=f[0]=1;
    for(int i=1;i<N;i++)
    {
        q[i]=(ll)q[i-1]*i%mod;//处理出所有的前缀乘
        f[i]=(ll)f[i-1]*qmi(i,mod-2)%mod;//逆元$(a*b)^{-1}=a^{-1}*b^{-1}$
    }
}
int main()
{
    init();
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",(ll)q[a]*f[a-b]%mod*f[b]%mod);
    }
    return 0;
}

求组合数 III

题目链接

给定n组询问,每组询问给定三个整数a,b,p,其中p是质数,请你输出$C_a^b$ mod p的值。

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

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

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

数据范围
1≤n≤20,
1≤b≤a≤1e18,
1≤p≤1e5,

输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2

分析:
这道题的数据范围较大且P不确定,不能够预处理,P的范围为1≤p≤1e5,这道题主要用到了Lucas定理,有公式 C a b   m o d   p = C a   m o d   p b   m o d   p ∗ C a / p b / p C_a^b\ mod\ p=C_{a\ mod\ p}^{b\ mod\ p}*C_{a/p}^{b/p} Cab mod p=Ca mod pb mod pCa/pb/p,根据这个公式可以求得组合数。
代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a,b;
int p;
int qmi(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=(ll)res*a%p;
        a=(ll)a*a%p;
        b>>=1;
    }
    return res;
}
int C(int a,int b)
{
    int res=1;
    for(int i=1,j=a;i<=b;j--,i++)
    {
        res=(ll)res*j%p*qmi(i,p-2)%p;
    }
    return res;
}
int lucas(ll a,ll b)
{
    if(a<p&&b<p) return C(a,b);
    else return (ll)C(a%p,b%p)*lucas(a/p,b/p)%p;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld%lld%d",&a,&b,&p);
        printf("%d\n",lucas(a,b));
    }
    return 0;
}

求组合数 IV

题目链接

输入a,b,求$C_a^b$的值。

注意结果可能很大,需要使用高精度计算。

输入格式
共一行,包含两个整数a和b。

输出格式
共一行,输出$C_a^b$的值。

数据范围
1≤b≤a≤5000
输入样例:
5 3
输出样例:
10

分析:
这道题不需要对结果取余,结果的范围较大,需要用到高精度算法这里我们可以将运算过程简化,如果做大数乘法和除法得话,比较麻烦,可以将 a ! a! a! , ( a − b ) ! (a-b)! (ab)! , b ! b! b!质因子分解,消除其相同的质因子,只需要进行大数乘法即可。
代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=5010;
int primes[N],cnt=0;
int sum[N];
bool f[N];
void get_primes(int n)//线性筛
{
    for(int i=2;i<=n;i++)
    {
        if(!f[i]) primes[cnt++]=i;
        for(int j=0;primes[j]<=n/i;j++)
        {
            f[primes[j]*i]=true;
            if(i%primes[j]==0) break;
        }
    }
}
int get_p(int n,int x)//计算n!中有几个x
{
    int res=0;
    while(n)
    {
        res+=n/x;
        n/=x;
    }
    return res;
}
vector<int> mul(vector<int> res,int x)//大数乘法
{
    int t=0;
    vector<int>c;
    for(int i=0;i<res.size();i++)
    {
        t+=res[i]*x;
        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 i=0;i<cnt;i++)//获得每个质数的数量
    {
        sum[i]=get_p(a,primes[i])-get_p(a-b,primes[i])-get_p(b,primes[i]);
    }
    vector<int>res;
    res.push_back(1);
    for(int i=0;i<=cnt;i++)
        for(int j=0;j<sum[i];j++)//大数运算
            res=mul(res,primes[i]);
    for(int i=res.size()-1;i>=0;i--)
        printf("%d",res[i]);
    printf("\n");
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

chp的博客

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

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

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

打赏作者

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

抵扣说明:

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

余额充值