组合数的求解(打表,逆元,Lucas 定理,大整数求解)

885. 求组合数 I

给定 n

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

的值。

输入格式

第一行包含整数 n

接下来 n

行,每行包含一组 a 和 b

输出格式

共 n

行,每行输出一个询问的解。

数据范围

1≤n≤10000

,
1≤b≤a≤2000

输入样例:

3
3 1
5 3
2 2

输出样例:

3
10
1

 coding:

由于1 ≤ b ≤ a ≤ 2000;所以可以用二维数组把所有组合数的值都存储下来,

/**
 * 由于1 ≤ b ≤ a ≤ 2000;所以可以用二维数组把所有组合数的值都存储下来,
*/

#include <iostream>

using namespace std;

const int maxn=2010 , mod=1e9+7;
int C[maxn][maxn];

void init()
{
    for(int i=0;i<maxn;++i)
    {
        C[i][0]=1;
        C[i][i]=1;
    }
    
    for(int i=2;i<maxn;++i)
        for(int j=1;j<=i/2;++j)
        {
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
            C[i][i-j]=C[i][j];
        }
}

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

886. 求组合数 II

给定 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

假设 a,b,m都是整数,m>1,且ab与1模m同余,则a与b互为模m的逆元。
 * 所以当m为质数的时候,a的m-2次方就是a模m的逆元。(费马小定理)。
 *
 * C(a,b)%mod= a!/(b!*(a-b)!)%mod;
 * 由于1 ≤ b ≤ a ≤ 10^5,所以可以预处理出fac[maxn],infac[maxn](阶乘的结果,阶乘的逆元的结果)
 * 结果直接用逆元进行计算。

/**
 * 假设 a,b,m都是整数,m>1,且ab与1模m同余,则a与b互为模m的逆元。
 * 所以当m为质数的时候,a的m-2次方就是a模m的逆元。(费马小定理)。
 * 
 * C(a,b)%mod= a!/(b!*(a-b)!)%mod;
 * 由于1 ≤ b ≤ a ≤ 10^5,所以可以预处理出fac[maxn],infac[maxn](阶乘的结果,阶乘的逆元的结果)
 * 结果直接用逆元进行计算。
*/

#include <iostream>

using namespace std;

typedef long long LL;

const int maxn=1e5+10,mod=1e9+7;
int fac[maxn],infac[maxn];

int qmi(int a,int b,int p) //a^b%p
{
    int res=1;
    while(b)
    {
        if(b&1)
            res=(LL)res*a%p;
        a=(LL)a*a%p;
        b>>=1;
    }
    
    return res;
}

void init()
{
    fac[0]=infac[0]=1;
    for(int i=1;i<maxn;++i)  //求阶乘的结果及阶乘的逆元的结果
    {
        fac[i]=(LL)fac[i-1]*i%mod;
        infac[i]=(LL)infac[i-1]*qmi(i,mod-2,mod)%mod;
    }
}

int main()
{
    init();
    int n;
    cin >> n;
    while (n -- )
    {
        int a,b;
        cin >> a >> b;
        cout << (LL)fac[a] * infac[b] % mod * infac[a-b] % mod << endl;
    }
    
    return 0;
}

887. 求组合数 III

给定 n

组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 Cbamodp

的值。

输入格式

第一行包含整数 n

接下来 n

行,每行包含一组 a,b,p

输出格式

共 n

行,每行输出一个询问的解。

数据范围

1≤n≤20

,
1≤b≤a≤1018,
1≤p≤105

,

输入样例:

3
5 3 7
3 1 5
6 4 13

输出样例:

3
3
2

 证明如下:

 

 

 

#include <iostream>

using namespace std;

typedef long long LL;

int qmi(int a,int b,int p)
{
    int res=1;
    while(b)
    {
        if(b&1)
            res=(LL)res*a%p;
        a=(LL)a*a%p;
        b>>=1;
    }
    return res;
}

int CP(int a,int b,int p)
{
    int res=1;
    for(int i=1;i<=b;++i)
    {
        res=(LL)res*(a-b+i) % p;
        res=(LL)res*qmi(i,p-2,p) % p;  //根据费马小定理用逆元求解
    }
    
    return res;
}

int Lucas(LL a,LL b,int p)
{
     if(a<p&&b<p)   
        return CP(a,b,p);
    else
        return (LL)CP(a%p,b%p,p) * Lucas(a/p,b/p,p) % p;
}

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        LL a,b;
        int p;
        cin >> a >> b >>p;
        cout << Lucas(a,b,p) << endl;
    }
    
    return 0;
}

将求逆元的过程拿到for循环外面能快一个数量级不止。

如果CP函数这么写,还能快上一倍:

int CP(int a,int b,int p)
{
    if(a<b)  //用这种方法求结果,一定需要判断a是否小于b,否则得到的答案也许就是错误
        return 0;
    int mul=a-b;  //假设b < a-b
    if((a-b) < b) //如果a-b < a,那么还得改变mul和b 的值;(原因是使求阶乘的运算步骤最少)
    {
        mul=b;
        b=a-b;
    }
    
    LL x=1,y=1;
    for(int i=1;i<=b;++i)
    {
        x=(mul+i)*x%p;  //先分别求分子与分母的阶乘,最后再用逆元求结果
        y=y*i%p;
    }
    
    return x*qmi(y,p-2,p) %p;  //用逆元求结果
}

 将求逆元的过程拿到for循环外面:

#include <iostream>

using namespace std;

typedef long long LL;

int qmi(int a,int b,int p)
{
    int res=1;
    while(b)
    {
        if(b&1)
            res=(LL)res*a%p;
        a=(LL)a*a%p;
        b>>=1;
    }
    return res;
}

int CP(int a,int b,int p)
{
    LL x=1,y=1;
    for(int i=1;i<=b;++i)
    {
        x=x*(a-b+i) % p;
        y=y*i % p;
    }
    
    return x*qmi(y,p-2,p)%p;
}

int Lucas(LL a,LL b,int p)
{
     if(a<p&&b<p)   
        return CP(a,b,p);
    else
        return (LL)CP(a%p,b%p,p) * Lucas(a/p,b/p,p) % p;
}

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        LL a,b;
        int p;
        cin >> a >> b >>p;
        cout << Lucas(a,b,p) << endl;
    }
    
    return 0;
}

888. 求组合数 IV

输入 a,b

,求 Cba

的值。

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

输入格式

共一行,包含两个整数 a

和 b

输出格式

共一行,输出 Cba

的值。

数据范围

1≤b≤a≤5000

输入样例:

5 3

输出样例:

10

C(a,b) 是个整数,那么我们一定能进行质因数分解;
 * 同样,C(a,b)=a!/(b!*(a-b)!),我们可以把分母与分子也进行质因数分解,
 * 将分子与分母相同的质因子进行指数相减,最后将所有得到的质因数进行相乘,
 * 就能得到最后的结果。
 *
 * 将分子与分母相同的质因子进行指数相减得到的指数一定是大于等于0的吗?
 * 证明:假定存在质因子相减得到的指数小于0的质因子,那么分母一定存在一个
 * 质因子,它与任何质因子都是互质的,因此C(a,b) 的结果就以应该是小数,而不是
 * 整数,这与C(a,b) 应该为整数矛盾,所以将分子与分母相同的质因子进行指数相减
 * 得到的指数一定是大于等于0的,得证!

/**
 * C(a,b) 是个整数,那么我们一定能进行质因数分解;
 * 同样,C(a,b)=a!/(b!*(a-b)!),我们可以把分母与分子也进行质因数分解,
 * 将分子与分母相同的质因子进行指数相减,最后将所有得到的质因数进行相乘,
 * 就能得到最后的结果。
 * 
 * 将分子与分母相同的质因子进行指数相减得到的指数一定是大于等于0的吗?
 * 证明:假定存在质因子相减得到的指数小于0的质因子,那么分母一定存在一个
 * 质因子,它与任何质因子都是互质的,因此C(a,b) 的结果就以应该是小数,而不是
 * 整数,这与C(a,b) 应该为整数矛盾,所以将分子与分母相同的质因子进行指数相减
 * 得到的指数一定是大于等于0的,得证!
*/

#include <iostream>
#include <vector>

using namespace std;

const int maxn=5010;
int p[maxn] ,num=0 , sum[maxn]; //p存素数,sum素数的指数
bool hs[maxn];

void get_primer(int n); //获得n以内的素数
int cal(int a,int p); //a! 内有多少个 质因子p
void get_pow(int a,int b);  //获得C(a,b)内的 质因子的指数
vector<int> mul(vector<int> &A,int b); //高精度与整数的乘法

int main()
{
    int a,b;
    cin >> a >> b;
    
    get_primer(a); 
    get_pow(a,b);
    
    vector<int> res;
    res.push_back(1);
    
    for(int i=0;i<num;++i)
        for(int j=0;j<sum[i];++j)
            res=mul(res,p[i]);
            
    for(int i=res.size()-1;i>=0;--i)
        cout << res[i];
    return 0;
}

void get_primer(int n) //获得n以内的素数
{
    for(int i=2;i<=n;++i)
    {
        if(!hs[i])
            p[num++]=i;
        for(int j=0;p[j] <= n/i;++j)
        {
            hs[p[j]*i]=1;
            if(i%p[j]==0)
                break;
        }
    }
}

int cal(int a,int p) // a! 内有多少个 质因子p
{
    if(a<p)
        return 0;
    else
        return cal(a/p,p) + a/p ;
}

void get_pow(int a,int b)  //获得C(a,b)内的 质因子的指数
{
    for(int i=0;i<num;++i)
    {
        int cnt=cal(a,p[i]) - cal(a-b,p[i]) - cal(b,p[i]);
        sum[i]=cnt;
    }
}

vector<int> mul(vector<int> &A,int b) //高精度与整数的乘法
{
    vector<int> res;
    int d=0;
    for(int i=0;i<A.size();++i)
    {
        d+=A[i]*b;
        res.push_back(d%10);
        d/=10;
    }
    
    while(d)
    {
        res.push_back(d%10);
        d/=10;
    }
    
    return res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值