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

被折叠的 条评论
为什么被折叠?



