zoj3982 Expected Waiting Time(卡特兰数)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3982


oj自带十倍常数,我也是很服气的

首先我们不难发现,每次选的出去的选手无论是谁,只要AS序列固定,总体的等待时间是不变的,所以,我们可以指定每次选的人是最后进房间的,设第i个卡特兰数为h(i),那么方案总数就是卡特兰数h(n)

然后理性分析一下,肯定是要按照位置算贡献的,对于每一个位置有两种情况,出栈和入栈,假设位置i是出栈的情况,那么我们枚举所有可能的入栈位置j(j<i),很容易发现,假设j与i之间有k个位置,那么如果某个元素从j入栈,从i出栈,那么j和i之间的k的个位置必须满足中间的所有元素入栈一遍,出栈一遍,很显然,k一定是个偶数,而且方案数为h(k/2),然后对于剩下的2*n-k-2个元素,他们的入栈出栈的方案数为h((2*n-k-2)/2),则对于位置i出栈,他的贡献为所有满足条件的k,sigma(h(k/2)*h((2*n-k-2)/2)),同理我们可以得到i为入栈时候的贡献,只不过这时候的贡献是负的。然后我们发现,当我们计算出对于i出栈的所有情况之后,i+2出栈的所有情况只比i出栈的所有情况多了一项,那么我们就可以dp方案数,然后每个位置算贡献就好了。


代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
typedef long long ll;
ll a[MAXN*2],b[MAXN*2],f[MAXN],h[MAXN],dp[MAXN*2];
inline ll qpow(int a,int b,int p)
{
	ll ret=1;
	for(a%=p;b;b>>=1)
	{
		if(b&1) ret=1LL*ret*a%p;
		a=1LL*a*a%p;
	}
	return ret;
}
void init(int n,int MOD)
{
	f[0]=0;f[1]=1;
	for(int i=2;i<=n+1;i++)
		f[i]=(1LL*((MOD-MOD/i)%MOD)*(f[MOD%i]%MOD))%MOD;
	h[0]=1;h[1]=1;
	for(int i=2;i<=n;i++)
		h[i]=1LL*h[i-1]*(4*i-2)%MOD*f[i+1]%MOD;
}
void solve()
{
	int n,p,A,B;
	scanf("%d%d%d%d%d",&n,&p,&b[0],&A,&B);
	init(n,p);
	a[0]=0;
	for(int i=1;i<=2*n;i++)
	{
		b[i]=(1LL*A*b[i-1]+B)%p;
		a[i]=a[i-1]+b[i]+1;
	}
	ll ans=0;
	dp[0]=dp[1]=0;
	for(int i=2;i<=2*n;i++)
	{
		int j=(i>>1)-1;
		dp[i]=(dp[i-2]+1LL*h[j]*h[n-1-j]%p)%p;
		ans=(ans+1LL*a[i]%p*dp[i]%p)%p;
	}
	for(int i=2*n-1;i>=1;i--)
	{
		ans=(ans-1LL*a[i]%p*dp[2*n+1-i]%p+p)%p;
	}
	ans=1LL*ans*qpow(h[n],p-2,p)%p;
	printf("%lld\n",(ans+p)%p);
}
int main()
{
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		solve();
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值