CF896D Nephren Runs a Cinema(卡特兰数&不互素数取模下除法)

37 篇文章 1 订阅
12 篇文章 0 订阅

Nephren Runs a Cinema(卡特兰数&不互素数求逆元)

题目大意

要求构造长度为n的序列 { a n } \{a_n\} {an}(令其和序列为 S n S_n Sn)使得有
{ ∀ i   S i ≥ 0 ∀ i a i = 0   o r   1   o r − 1 l   ≤ S n ≤ r \begin{cases}\forall_i \ S_i\ge 0\\\forall_i a_i=0\ or\ 1\ or -1\\l\ \le S_n\le r\end{cases} i Si0iai=0 or 1 or1l Snr
问可以构造多少这样的序列.由于最终的答案可能很大,因此对p取模

解题思路

考虑现在有i个0,则先将i个0分配到序列中,剩余未设定的数的数量设为m.则在着m个未设定数字中只有1与-1.对一确定的长度m,要让其所有的前缀和大于0,且有最终值介于lr之间,设最终达到的值为2x.设m=2k,则1有k+x,-1有k-x.只考虑最终的值为x则有组合数 C 2 k k + x C_{2k}^{k+x} C2kk+x。但是这并不是最终答案,我们还需要减去其中会小于0的情况.如果其中存在少于0的部分,则必然存在某个位置2h+1此时有h+1个-1,h个1,而剩下的序列也就会剩下 k + x − h k+x-h k+xh个1, k − x − h − 1 k-x-h-1 kxh1个-1,将-1与1数量翻转,则整个序列将存在 k + x + 1 k+x+1 k+x+1个-1. k − x − 1 k-x-1 kx1个1,则有少于0的前缀和的组合数即为 C 2 k k + x + 1 C_{2k}^{k+x+1} C2kk+x+1。则最终答案介于lr之间的组合数就是 ∑ x = l r ( C 2 k k + x − C 2 k k + x + 1 ) \sum_{x=l}^{r}(C_{2k}^{k+x}-C_{2k}^{k+x+1}) x=lr(C2kk+xC2kk+x+1)则最终的求和公式即为 C 2 k k + ( l + 1 ) / 2 − C 2 k k + r / 2 + 1 C_{2k}^{k+(l+1)/2}-C_{2k}^{k+r/2+1} C2kk+(l+1)/2C2kk+r/2+1以上的其实可以算作是卡特兰数的一个拓展.而在组合数的计算中,由于需要取模故需要求逆元,但是,并不能保证模数与阶乘互素,因此需要对阶乘提出所有与模数的公因数.将其额外提出运算,而剩余部分直接做逆元.

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);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值