1. 程式人生 > 實用技巧 >BZOJ-1485 [HNOI2009]有趣的數列(卡特蘭數+階乘分解)

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\) 中包含這個質因子的數的數量。則 \(cnt1-cnt2-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;
}