数学知识:求组合数

 求组合数 I:

注意:1~1w组数据

#include<iostream>
using namespace std;

const int mod = 1e9+7;
long long f[2010][2010];

int main()
{
    int n;
    scanf("%d",&n);
    //预处理
    for(int i=0;i<=2000;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(!j) f[i][j]=1;
            else f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod;//如上图公式所得
        }
    }

    while(n--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%lld\n",f[a][b]);
    }
}

求组合数 II:

注意:1~10w组数据

#include<iostream>
using namespace std;

const int mod=1e9+7,N=1e5+10;
typedef long long LL;
long long fac[N],infac[N];//fac[N]阶乘mod p的值,infac[N]阶乘的逆元mod p的值

int quick_pow(int a, int k, int p)//快速幂求逆元
{
    int res = 1;
    while (k)//对k进行二进制化,从低位到高位
    {
        //如果k的二进制表示的第0位为1,则乘上当前的a
        if (k & 1) res = (LL)res * a % p;
        //k右移一位
        k >>= 1;
        //更新a
        a = (LL)a * a % p;
    }
    return res;
    //a:4 b:3 p:9
//二进制b   11        1
      //res 1*4%9=4   4*7%9=1     
      //b   1         0        
      //a   4*4%9=7   16*16%9=112
      //因为:2^0=1,2^1=2,2^2=4,2^3=8...
      //根据公式:b=3,所以2^0+2^1=b,与这个指数上面的b算的一样
      //所以(4^2^0%9*4^2^1%9)%9=(4*7)%9=28%9=1,这个就是上面的res
}

int main()
{
    int n;
    fac[0]=infac[0]=1;
    for(int i=1;i<=1e5;i++)
    {
        fac[i]=fac[i-1]*i%mod;//i的阶乘就是上一个阶乘*i
        infac[i]=(LL)infac[i - 1] * quick_pow(i,mod-2,mod)%mod;//i的逆元阶乘就是上一个数的逆元阶乘*i的逆元
    }
    scanf("%d",&n);
    while(n--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%lld\n",(LL)fac[a] * infac[b] % mod * infac[a - b] % mod);//由上图公式得
    }
}

求组合数 III:

注意:1~20w组数据

#include<iostream>
#include<algorithm>

using namespace std;

typedef long long LL;

int qmi(int a,int k,int p)//快速幂求逆元
{
    int res = 1;
    while(k)
    {
        //如果k的二进制表示的第0位为1,则乘上当前的a
        if(k&1)res = (LL)res*a%p;
        //k右移一位
        k>>=1;
        //更新a
        a = (LL)a*a%p;
    }
    return res;
}

int C(int a,int b,int p)//自变量类型int
{
    if(b>a)return 0;//边界条件
    int res = 1;
    // a!/(b!*(a-b)!) = (a-b+1)*...*a / b! 分子有b项,这是组合公式
    for(int i=1,j=a;i<=b;i++,j--)//图二所示公式,递推的每次乘a然后除以b, 因为从a到a−b+1, 所以就是乘b次
    {
        res = (LL)res*j%p;//乘a,此时a就是j
        res = (LL)res*qmi(i,p-2,p)%p;//除以b,就是*b的逆元,此时i就是b
    }
    return res;
}
//对公式敲
int lucas(LL a,LL b,int p)
{
    if(a<p && b<p)return C(a,b,p);//lucas递归终点是C()
    return (LL)lucas(a/p,b/p,p)*C(a%p,b%p,p)%p;//这是lucas公式,也是最后需要的答案,a%p后肯定是<p的,所以可以用C(),但a/p后不一定<p 所以用lucas继续递归
}

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

求组合数 IV:

注意:答案可能很大,需要使用高精度计算

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

const int N=5010;

int primes[N],cnt;//primes[N]用来存放质数,cnt代表质数的个数
int sum[N];//每个质数的次方
bool st[N];//st[i], i为质数则为false否则为true,默认全是质数

void get_primes(int n)//线性筛质数,即筛出每个p
{
    for(int i=2;i<=n;i++)//i是某个数的意思,要找2~n的所有数是否为质数
    {
        if(!st[i])primes[cnt++]=i;//如果i该数是质数,则放进数组,cnt++代表质数个数加一
        for(int j=0;primes[j]<=n/i;j++)//把小于n的合数都筛了
        {
            st[primes[j]*i]=true;//i无论是质数还是合数,它的质数倍数都可以赋true
            //例:i=2时,找到i的最小质因子为2,所以2*2=4被赋true
            //    i=3时,找到i的最小质因子为3,所以3*3=9被赋true
            //    i=4时,前面已经找到4为合数所以不会让上面的if出错
            //像这样,不会让任何i和合数的情况下未被赋true,而导致上面的if出错
 
            //当primes[j]是i的最小质因子,说明primes[j]一定是i的倍数primes[j]*i的最小质因子
            //则说明对于i的倍数primes[j+k]*i的最小质因子也是primes[j],而非primes[j+k]
            //后面的任何i的倍数都不用看了,直接break,让i++找下一个数是否为质数
            
            //当i%primes[j]!=0时,说明此时遍历到的primes[j]不是i的质因子,所以primes[j]*i的最小质因子不是primes[j]
            //可以继续找primes[j+k]的i倍是否为最小质因子
            if(i%primes[j]==0)break;
        }
    }
}
//计算n的阶乘中<=n的p的指数最大为多少,如2,2^2,2^3...,算最大为几次方
int get(int n,int p)
{
    int res =0;
    while(n)
    {
        res+=n/p;
        n/=p;
    }
    return res;
}
//高精度乘法把所有质因子乘起来
vector<int> mul(vector<int> a, int b)
{
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size() || t; i++)//只要有进位或者A没读完,就继续
	{
		if (i < a.size())t += a[i] * b;//乘法,A是低位在前,所以先算的低位
		c.push_back(t % 10);//把余数放进来
		t /= 10;//给下一位的进位
    }
    return c;
}

int main()
{
    int a,b;
    scanf("%d%d",&a,&b);
    
    get_primes(a);//找到1~a的所有质数

    for(int i=0;i<cnt;i++)//把所有的质数算出a的阶乘中该质数的最大次方为多少
    {
        int p = primes[i];
        //当前i这个数包含的p的个数=a的阶乘的p的个数-(a-b)的阶乘的p的个数-b的阶乘的p的个数
        //用分子里面 p 的次方减去分母里面 p 的次方。这里的计算组合数的公式为a!/(b!*(a-b)!),因此用 a 里面 p 的次方减去 b 里面 p 的次方和 (a-b) 里面 p 的次方。
        sum[i] = get(a,p)-get(a-b,p)-get(b,p);//是a-b不是b-a
    }

    vector<int> res;
    res.push_back(1);

    for (int i = 0; i < cnt; i ++ )//每个质数
        for (int j = 0; j < sum[i]; j ++ )//primes[i]的次方,即每个质数的次方
            res = mul(res, primes[i]);//高精度乘法

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

    return 0;
}

满足条件的01序列:

将 01 序列置于坐标系中,起点定于原点。若 0 表示向右走,1 表示向上走,那么任何前缀中 0 的个数不少于 1的个数就转化为,路径上的任意一点,横坐标大于等于纵坐标。题目所求即为这样的合法路径数量。

答案如图,即卡特兰数。

#include <iostream>
using namespace std;

typedef long long LL;

const int N = 200010, mod = 1e9 + 7;

int n;
int fact[N], infact[N];//fac[N]阶乘mod p的值,infac[N]阶乘的逆元mod p的值

int ksm(int a, int k,int mod) //快速幂求逆元
{
    int res = 1;
    while (k) //对k进行二进制化,从低位到高位
    {
        if (k & 1) res = (LL)res * a % mod;//如果k的二进制表示的第0位为1,则乘上当前的a
        k >>= 1;//k右移一位
        a = (LL)a * a % mod;//更新a
    }
    return res;
        //a:4 b:3 p:9
//二进制b   11        1
      //res 1*4%9=4   4*7%9=1     
      //b   1         0        
      //a   4*4%9=7   16*16%9=112
      //因为:2^0=1,2^1=2,2^2=4,2^3=8...
      //根据公式:b=3,所以2^0+2^1=b,与这个指数上面的b算的一样
      //所以(4^2^0%9*4^2^1%9)%9=(4*7)%9=28%9=1,这个就是上面的res
}

int main() 
{
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i++) 
    {
        fact[i] = (LL)fact[i - 1] * i % mod;//i的阶乘就是上一个阶乘*i
        infact[i] = (LL)infact[i - 1] * ksm(i, mod - 2,mod) % mod;//i的逆元阶乘就是上一个数的逆元阶乘*i的逆元
    }
    scanf("%d",&n);
    //由卡特兰公式:2n!/n!/n!/n+1,这里的除以n+1可以写成乘以n+1的逆元
    int res = (LL)fact[2 * n] * infact[n] % mod * infact[n] % mod * ksm(n + 1, mod - 2,mod) % mod;
    printf("%d\n",res);
    return 0;
}

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@katoumegumi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值