小a的学期(组合数很大,取模不为质数)

这是一道来源于牛客网的题目,小a需要安排2n天的学期计划,使得在任何一天x之前,没写作业的天数减去写作业的天数至少为k的非法方案不超过n。问题转化为计算特定条件下的组合数取模,当模数不是质数时,利用卢卡斯定理和组合数的质因子分解简化计算。

链接:https://ac.nowcoder.com/acm/contest/317/H
来源:牛客网
 

题目描述

小a是一个健忘的人,由于他经常忘记做作业,因此老师对他很恼火。
小a马上就要开学了,他学期一共2n天,对于第ii天,他有可能写了作业,也可能没写作业,不过他自己心里还有点B数,因此他会写恰好n天的作业
现在,小a需要安排他的学期计划,如果小a的学期中存在一天x,在这之前的x天中,他没写作业的天数 - 写作业的天数⩾k,那么老师就会把它开除,我们称这是一种不合法的方案
小a想知道他有多少种合法的方案

输入描述:

第一行三个整数n,k,pn,k,p,pp表示对pp取模

输出描述:

一个整数表示答案

示例1

输入

复制

2 1 100007

输出

复制

2

说明

总共有2n=42n=4天
合法的方案有
写了 没写 写了 没写
写了 写了 没写 没写
注意:没写 写了 没写 写了 是一种不合法的方案,因为在第一天时没写的天数-写了的天数⩾1⩾1

示例2

输入

复制

10 5 10000007

输出

复制

169252

备注:

1⩽n,k⩽106,p⩽109+71⩽n,k⩽106,p⩽109+7
不保证pp为质数!

 

思路:

这个题是很久之前做的题目,这是一种处理组合数取模的方法。

这个题目是卡特兰数的变形。

一般的卡特兰数是这道题目中的K等于1的情况。

所以可以根据卡特兰数的公式得出这道题目的公式C(2*n,n)-C(2*n,n+k)。

然后,现在的主要问题就变成了,如何把这个数算出来,如果题目取模为质数,就可以用卢卡斯求出来。

但是这里不是质数。所以可以把每个组合数展开来求,求出每个分子分母的  质因子和次数,然后约分之后,用快速幂求出整个组合数。这样就不用因为有除法和取模,需要求很多逆元了。大大减少了计算的时间。

代码:

 

#include <bits/stdc++.h>
 
using namespace std;
#define ll long long
const int N=2e6+100;
 
int primes[N], cnt;
int st[N];
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; j < cnt && i * primes[j] <= n; j ++ )
        {
            st[primes[j] * i] = primes[j];
            if (i % primes[j] == 0) break;
        }
    }
}
 
ll pow_mod(ll a,ll b,ll m)
{
    a=a%m;
    ll ans=1;
    while(b)
    {
        if(b&1)
        {
            ans=(ans*a)%m;
            b--;
        }
        b>>=1;
        a=a*a%m;
    }
    return ans;
}
 
 
int sum[N];
 
void add(int x,int v)
{
    while(st[x])
    {
        sum[st[x]]+=v;
        x/=st[x];
    }
    if(x>1)sum[x]+=v;
}
 
ll C(int n,int m,int p)
{
    memset(sum,0,sizeof(sum));
    for(int i=n,j=0;j<m;j++,i--)add(i,1);
    for(int i=1;i<=m;i++)add(i,-1);
    ll res=1;
    for(int i=2;i<=n;i++)
    {
        if(sum[i])res=(res*pow_mod(i,sum[i],p))%p;
    }
    return res;
}
 
int main()
{
 
    int n,k,p;
 
    scanf("%d%d%d",&n,&k,&p);
    get_primes(2*n);
    ll res=(C(2*n,n,p)-C(2*n,n+k,p)+p)%p;
    printf("%d\n",res);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值