1. 程式人生 > >[HNOI2009]有趣的數列

[HNOI2009]有趣的數列

數列 第一個 str namespace http csdn ios ram span

題目描述

我們稱一個長度為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的值。

輸入輸出格式

輸入格式:

輸入文件只包含用空格隔開的兩個整數n和P。輸入數據保證,50%的數據滿足n<=1000,100%的數據滿足n<=1000000且P<=1000000000。

輸出格式:

僅含一個整數,表示不同的長度為2n的有趣的數列個數mod P的值。

輸入輸出樣例

輸入樣例#1:
3 10
輸出樣例#1:
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)。
50分:最先想到動態規劃
f[i][j]表示i位為j的方案數,轉移方程略
100分:catlan數

考慮將數列分成奇數項數列和偶數項數列,將2n個數從大到小往兩個數列裏放這樣可以滿足兩個數列都是遞增的
那麽只要第一個數列不是滿的,任何時候都可以放數進去,而第二個數列放數進去時要滿足與當前項對應的第一個數列的那一項已經放了數,這樣才能滿足對應的奇數項小於偶數項 (感覺和棧很像)
那麽只要計算這種放法下的方案數就是序列的方案數,而這種放法和Catalan數的經典問題括號序列方案數的那個其實是一樣的,將一個數放入奇數序列等價為放一個(

,放入偶數序列等價為放一個),將所有左右括號匹配,第一項一定是(,設他和第i項匹配,那麽方案數就是兩個括號中間的方案數乘第i項右邊的方案數,即

f[n]=f[0]?f[n?2]+f[2]?f[n?4]......
將這裏所有數除以2,就是第n項Catalan數,所以長度為2n的數列的方案數就是第n項Catalan數,因為P不是質數,所以分解質因數弄一下
--部分轉載L_0_Forever_LF的專欄
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5
#include<cmath> 6 using namespace std; 7 long long p,n,ans; 8 bool vis[3000001]; 9 int mp[3000001],prime[2000001],tot,cnt[3000001]; 10 long long qpow(long long x,long long m) 11 { 12 if (m==0) return 1; 13 long long tmp=qpow(x,m/2); 14 tmp=(tmp*tmp)%p; 15 if (m%2==1) tmp=(tmp*x)%p; 16 return tmp; 17 } 18 int main() 19 {int i,j; 20 cin>>n>>p; 21 for (i=2;i<=2*n;i++) 22 { 23 if (vis[i]==0) 24 {mp[i]=i; 25 prime[++tot]=i; 26 } 27 for (j=1;j<=tot&&i*prime[j]<=2*n;j++) 28 { 29 vis[i*prime[j]]=1; 30 mp[i*prime[j]]=prime[j]; 31 if (!(i%prime[j])) break; 32 } 33 } 34 for (i=2;i<=n;i++) 35 cnt[i]=-1; 36 for (i=n+2;i<=2*n;i++) 37 cnt[i]=1; 38 for (i=2*n;i>=2;i--) 39 { 40 if (mp[i]!=i) 41 { 42 cnt[i/mp[i]]+=cnt[i]; 43 cnt[mp[i]]+=cnt[i]; 44 } 45 } 46 int ans=1; 47 for (i=2;i<=2*n;i++) 48 { 49 if (mp[i]==i) 50 ans=(ans*qpow(i,cnt[i]))%p; 51 } 52 cout<<ans; 53 }

[HNOI2009]有趣的數列