Nephren Runs a Cinema(卡特兰数&不互素数求逆元)
题目大意
要求构造长度为n的序列
(令其和序列为
)使得有
问可以构造多少这样的序列.由于最终的答案可能很大,因此对p取模
解题思路
考虑现在有i个0,则先将i个0分配到序列中,剩余未设定的数的数量设为m.则在着m个未设定数字中只有1与-1.对一确定的长度m,要让其所有的前缀和大于0,且有最终值介于lr之间,设最终达到的值为2x.设m=2k,则1有k+x,-1有k-x.只考虑最终的值为x则有组合数 。但是这并不是最终答案,我们还需要减去其中会小于0的情况.如果其中存在少于0的部分,则必然存在某个位置2h+1此时有h+1个-1,h个1,而剩下的序列也就会剩下 个1, 个-1,将-1与1数量翻转,则整个序列将存在 个-1. 个1,则有少于0的前缀和的组合数即为 。则最终答案介于lr之间的组合数就是 则最终的求和公式即为 以上的其实可以算作是卡特兰数的一个拓展.而在组合数的计算中,由于需要取模故需要求逆元,但是,并不能保证模数与阶乘互素,因此需要对阶乘提出所有与模数的公因数.将其额外提出运算,而剩余部分直接做逆元.
AC代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long LL;
const int size=1e5+5;
int n,p,l,r;
int tot;
int prime[45];
int fac[size],inv[size];
int tim[size][45];
LL quick_pow(LL a,LL b)
{
LL ans=1;
while(b)
{
if(b&1) ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
int phi(int n)
{
int res=n;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
res=res-res/i;
do n/=i;
while(n%i==0);
}
}
if(n>1)
res=res-res/n;
return res;
}
void init()
{
int temp=p;
for(int i=2;i*i<=temp;i++)
{
if(temp%i==0)
{
prime[tot++]=i;
do temp/=i;
while(temp%i==0);
}
}
if(temp>1) prime[tot++]=temp;
fac[0]=fac[1]=1;
inv[0]=inv[1]=1;
int phip=phi(p);
memset(tim,0,sizeof(tim));
for(int i=2;i<=n;i++)
{
int x=i;
for(int j=0;j<tot;j++)
{
tim[i][j]=tim[i-1][j];
while(x%prime[j]==0) x/=prime[j],tim[i][j]++;
}
fac[i]=fac[i-1]*x%p;
inv[i]=quick_pow(fac[i],phip-1);
}
}
int combi(int n,int m)
{
if(m>n) return 0;
if(m<0) return 0;
if(m==0) return 1;
LL ans=(fac[n]*inv[n-m]%p)*inv[m]%p;
for(int i=0;i<tot;i++)
{
ans=ans*quick_pow(prime[i],tim[n][i]-tim[n-m][i]-tim[m][i])%p;
}
return ans;
}
int32_t main()
{
scanf("%lld%lld%lld%lld",&n,&p,&l,&r);
LL ans=0;
init();
for(int i=0;i<=n;i++)
{
int m=n-i;
ans=(ans+combi(n,i)*((-combi(m,(m+min(r,m))/2+1)+combi(m,(m+l+1)/2)+p)%p))%p;
}
printf("%lld\n",ans);
}