BZOJ-1485 [HNOI2009]有趣的數列(卡特蘭數+階乘分解)
題目描述
我們稱一個長度為 \(2n\) 的數列是有趣的,當且僅當該數列滿足以下三個條件:
-
它是從 \(1\) ~ \(2n\) 共 \(2n\) 個整數的一個排列 \(a_i\)
-
所有的奇數項滿足 \(a_1<a_3<\cdots<a_{2n-1}\),所有的偶數項滿足 \(a_2<a_4<\cdots<a_{2n}\)
-
任意相鄰的兩項 \(a_{2i-1}\) 與 \(a_{2i}\) 滿足:\(a_{2i-1}<a_{2i}\)。
對於給定的 \(n\),求有多少個不同的長度為 \(2n\) 的有趣的數列,答案對 \(p\)
資料範圍:\(1\leq n\leq 10^6,1\leq p\leq 10^9\)。
分析
由於偶數項遞增,且偶數項大於相鄰奇數項,即偶數項大於前面所有的數字,也就是說偶數項的數字大於等於偶數項的下標。
考慮按照 \(1\) ~ \(2n\) 的順序依次放數字,可以發現每次只能放在下標最小的奇數或偶數位上,這樣才能保證數字遞增。假設當前在偶數位放置了 \(a\) 個數字,在奇數位放了 \(b\) 個數字,且 \(a>b\),一共放了 \(x=a+b\) 個數。則 \(a>\frac{x}{2}\),最後放置的偶數的下標為 \(2a\),但是 \(2a>x\)
卡特蘭數通項公式:
\[\frac{\dbinom{2n}{n}}{n+1}=\frac{(n+2)\times(n+3)\times\cdots\times(2n-1)\times2n}{n!} \]由於模數 \(p\) 不一定是質數,所以不能直接求逆元。首先篩出 \(1\) ~ \(2n\) 中的所有質數,列舉每個質數,計算 \(cnt1\):\(1\) ~ \(2n\) 中包含這個質因子的數的數量,\(cnt2\):\(1\) ~ \(n+1\) 中包含這個質因子的數的數量,\(cnt3\)
接下來考慮如何快速分解 \(1\) ~ \(n\) 中的質因子的數量:
如果把 \(1\) ~ \(n\) 中的每個數都分解質因數,再把結果合併,時間複雜度為 \(O(n\sqrt{n})\)。\(n!\) 中質因子 \(p\) 的個數等於 \(1\) ~ \(n\) 中每個數包含質因子 \(p\) 的個數之和。在 \(1\) ~ \(n\) 中,\(p\) 的倍數顯然有 \(\lfloor\frac{n}{p}\rfloor\) 個,\(p^2\) 的倍數有 \(\lfloor\frac{n}{p^2}\rfloor\) 個,\(\cdots\)。
綜上所述,\(n!\) 中質因子 \(p\) 的個數為:
\[\Big\lfloor\frac{n}{p}\Big\rfloor+\Big\lfloor\frac{n}{p^2}\Big\rfloor+\cdots+\Big\lfloor\frac{n}{p^{\lfloor\log_p n\rfloor}}\Big\rfloor=\sum_{p^k\leq n}\Big\lfloor\frac{n}{p^k}\Big\rfloor \]時間複雜度 \(O(n\log n)\)。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
long long n,p;
int prime[N+10],vis[N+10],cnt;
void init()
{
for(int i=2;i<=N;i++)
{
if(!vis[i])
prime[++cnt]=i;
for(int j=1;i*prime[j]<=N&&j<=cnt;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
break;
}
}
}
long long quick_pow(long long a,long long b,long long p)
{
long long ans=1;
while(b)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
long long divide(long long n,long long p)
{
long long ans=0;
while(n)
{
ans=ans+n/p;
n=n/p;
}
return ans;
}
int num[N];
int main()
{
init();
cin>>n>>p;
for(int i=1;i<=cnt;i++)
num[i]=divide(2*n,prime[i])-divide(n+1,prime[i])-divide(n,prime[i]);
long long ans=1;
for(int i=1;i<=cnt;i++)
ans=ans*quick_pow(prime[i],num[i],p)%p;
cout<<ans<<endl;
return 0;
}