[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}(1\leqslant i\leqslant 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(n\log 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\n",ans);
return 0;
}

浙公网安备 33010602011771号