bzoj 1485: [HNOI2009]有趣的数列 (卡特兰数)

1485: [HNOI2009]有趣的数列

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 1226   Solved: 652
[ Submit][ Status][ Discuss]

Description

 我们称一个长度为2n的数列是有趣的,当且仅当该数列满足以下三个条件:

    (1)它是从1到2n共2n个整数的一个排列{ai};

    (2)所有的奇数项满足a1<a3<…<a2n-1,所有的偶数项满足a2<a4<…<a2n

    (3)任意相邻的两项a2i-1与a2i(1≤i≤n)满足奇数项小于偶数项,即:a2i-1<a2i

    现在的任务是:对于给定的n,请求出有多少个不同的长度为2n的有趣的数列。因为最后的答案可能很大,所以只要求输出答案 mod P的值。

Input

输入文件只包含用空格隔开的两个整数n和P。输入数据保证,50%的数据满足n≤1000,100%的数据满足n≤1000000且P≤1000000000。

Output

仅含一个整数,表示不同的长度为2n的有趣的数列个数mod P的值。

Sample Input

3 10

Sample Output

5


对应的5个有趣的数列分别为(1,2,3,4,5,6),(1,2,3,5,4,6),(1,3,2,4,5,6),(1,3,2,5,4,6),(1,4,2,5,3,6)。

HINT

Source

[ Submit][ Status][ Discuss]


题解:卡特兰数

这道题打表找规律就会发现其实答案就是卡特兰数

f[n]=c(2n,n)/n+1=c(2n,n)-c(2n,n-1)

然后分解质因数再合并,即可处理。

但是这道题,除了打表以外,还有更科学的分析方法。可以将题目中的问题转换成卡特兰数的经典问题——入栈出栈问题。

确定了奇数位后,偶数位要么唯一确定,要么不存在合法的序列。 然后就可以将奇数位看成入栈,偶数位看成出栈,转换成卡特兰数的模型。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 2000003
#define LL long long
using namespace std;
int prime[N],pd[N],num[N],mp[N];
int n,p;
void init()
{
	for (int i=2;i<=2000000;i++){
		if (!pd[i]) prime[++prime[0]]=i,mp[i]=prime[0];
		for (int j=1;j<=prime[0];j++){
			if (prime[j]*i>2000000) break;
			pd[prime[j]*i]=1; 
			if (i%prime[j]==0) break;
		}
	}
}
void calc(int x,int val)
{
	int k=x;
	for (int i=1;prime[i]*prime[i]<=k;i++) 
	if (k%prime[i]==0){
		while (k%prime[i]==0) {
			k/=prime[i]; num[i]+=val;
		}
	}
	if (k>1) num[mp[k]]+=val;
}
LL quickpow(LL num,int x)
{
	LL base=num%p; LL ans=1;
	while (x) {
		if (x&1) ans=ans*base%p;
		x>>=1;
	    base=base*base%p;
	}
    return ans;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&p);
	init();
	for (int i=n+1;i<=2*n;i++) calc(i,1);
	for (int i=1;i<=n;i++) calc(i,-1);
	calc(n+1,-1);
	//for (int i=1;i<=n;i++)  cout<<num[i]<<" ";
	//cout<<endl;
	LL ans=1;
	for (int i=1;i<=prime[0];i++)
	 ans=ans*quickpow((LL)prime[i],num[i])%p;
	printf("%I64d\n",ans);
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值