[HNOI2009]有趣的数列

题目:洛谷P3200、BZOJ1485、codevs2337。

题目大意:
我们称一个长度为$2n$的数列是有趣的,当且仅当该数列满足以下三个条件:
1. 它是从$1$到$2n$共$2n$个整数的一个排列${a_i}$;
2. 所有的奇数项满足$a_1<a_3<dots<a_{2n-1}$,所有的偶数项满足$a_2<a_4<dots<a_{2n}$;
3. 任意相邻的两项$a{2i-1}$与$a_{2i}(1leqslant ileqslant n)$满足奇数项小于偶数项,即:$a{2i-1}<a{2i}$。
现在的任务是:对于给定的$n$,请求出有多少个不同的长度为$2n$的有趣的数列。因为最后的答案可能很大,所以只要求输出答案$mod P$的值。
解题思路:
找找规律,发现,答案是卡特兰数。
然而$P$并不一定是质数。
所以用另一个公式:$f_n =frac{C^n_{2n}}{n+1}=(2n)!div (n+1)!div n!$。
并且$f_i$保证是一个整数。
由于我们可以很快地求出$n!$内包含某个质数因子的数量(除一下就行了),所以,我们先求出$2n$以内的质数,然后套公式,统计每个质数因子包含的个数,然后快速幂加到答案里即可。
筛质数用线性筛即可。
时间复杂度$O(nlog n)$。

C++ Code:

#include<bits/stdc++.h>
#define LoveLive long long
int prm[1000005];
bool isnprm[2000005];
int n,md,cnt=0;
inline LoveLive pow(LoveLive a,int p){
	a%=md;
	LoveLive ans=1;
	while(p){
		if(p&1)ans=ans*a%md;
		a=a*a%md;
		p>>=1;
	}
	return ans;
}
int main(){
	scanf("%d%d",&n,&md);
	int N=n<<1;
	for(int i=2;i<=N;++i){
		if(!isnprm[i])prm[++cnt]=i;
		for(int j=1;j<=cnt&&prm[j]*i<=N;++j){
			isnprm[prm[j]*i]=1;
			if(i%prm[j]==0)break;
		}
	}
	LoveLive ans=1;
	for(int i=1,m;i<=cnt;++i){
		int s=0;
		for(m=N;m/=prm[i];)s+=m;
		for(m=n;m/=prm[i];)s-=m;
		for(m=n+1;m/=prm[i];)s-=m;
		ans=ans*pow(prm[i],s)%md;
	}
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Mrsrz/p/9041689.html