組合數取模的幾種方法--Exlucas&楊輝三角&組合

HANGRY_Sol&Cekas發表於2024-03-14

組合數取模的幾個方法

求:

\[C^{m}_{n} \bmod P \]

1.楊輝三角法

\[C^{m}_{n} = C^{m - 1}_{n - 1} + C^{ m }_{n - 1} \]

時間複雜度有點小高

2.若 \(n\) 比較大,但 \(P\) 比較小

使用 \(EXlucas\) 大法.

具體講解點這裡:戳我

3.若 \(n\) 比較小, \(P\) 炒幾大

我們考慮分解組合數:

原式為:

\[\frac{n!}{\left(n - m\right)! \times m! } \]

化簡為:

\[\frac{\prod_{i = m + 1}^{n}i}{\prod^{n - m}_{i=1}i} \]

考慮對每一個 \(i\) 分解質因數,但由於全分解的時間複雜度忒高,於是先將其最小的分出來。

開一個長度為 \(n\)\(cnt\) 陣列 \(cnt_i\) 表示 \(i\)\(cnt_i\) 次冪。

從大往小裡列舉,只要其不是質數,就考慮如下轉化:


cnt[i / num[i]] += cnt[i];

cnt[num[i]] += cnt[i];

易證其正確性。

下面一道例題: ( \(Catlan\) )

\(HNOI2009\) 有趣的數列

我們稱一個長度為 \(2n\) 的數列是有趣的,當且僅當該數列滿足以下三個條件:

  • 它是從 \(1 \sim 2n\)\(2n\) 個整數的一個排列 \(\{a_n\}_{n=1}^{2n}\)

  • 所有的奇數項滿足 \(a_1<a_3< \dots < a_{2n-1}\),所有的偶數項滿足 \(a_2<a_4< \dots <a_{2n}\)

  • 任意相鄰的兩項 \(a_{2i-1}\)\(a_{2i}\) 滿足:\(a_{2i-1}<a_{2i}\)

對於給定的 \(n\),請求出有多少個不同的長度為 \(2n\) 的有趣的數列。
因為最後的答案可能很大,所以只要求輸出答案對 \(p\) 取模。

輸入格式

一行兩個正整數 \(n,p\)

輸出格式

輸出一行一個整數表示答案。

樣例輸入

3 10

樣例輸出

5

資料範圍

對於 \(50\%\) 的資料,\(1\le n \le 1000\)
對於 \(100\%\) 的資料,\(1\le n \le 10^6\)\(1\le p \le 10^9\)

\(code\)

簡化題意,求:

\[\frac{C^{n}_{2n}}{n + 1} \]

易推出是卡特蘭數,程式碼如下:

114514
#include<bits/stdc++.h>
using namespace std ; 
#define int long long 
const int N = 1000010 ; 
int n , mod ; 
int num , prime[ 2 * N ] ; 
int Next[ N << 1 ] ; 
int cnt[ N << 1 ] ; 
inline int read( )
{
    int x = 0 , f = 1 ; 
    char c = getchar( ) ; 
    while ( c > '9' || c < '0' )
    {
        if( c == '-' ) 
        {
            f = -f ; 
        }
        c = getchar( ) ; 
    }
    while ( c >= '0' && c <= '9' ) 
    {
        x = x * 10 + c - '0' ; 
        c = getchar( ) ; 
    }
    return x * f ; 
}
inline int Regular_Quick_Pow( int a , int b ) 
{
    int ans = 1 ; 
    while ( b > 0 ) 
    {
        if ( b & 1 ) ans = ( ans * a ) % mod ; 
        b >>= 1 ; 
        a = ( a * a ) % mod ; 
    }
    return ans ; 
}
signed main( )
{
    #ifndef ONLINE_JUDGE
        freopen( "1.in" , "r" ,  stdin ) ; 
        freopen( "1.out" , "w" , stdout ) ; 
    #endif
    cin >> n >> mod ; 
    for ( int i = 2 ; i <= 2 * n ; ++ i ) 
    {
        if ( !Next[ i ] ) 
        {
            Next[ i ] = i ; 
            prime[ ++ num ] = i ; 
        }
        for ( int j = 1 ; j <= num && prime[ j ] * i <= 2 * n ; ++ j )
        {
            Next[ prime[ j ] * i ] = min( prime[ j ] , Next[ i ] ) ; 
        } 
    } 
    Next[ 1 ] = 1 ; 
    for ( int i = n + 2 ; i <= 2 * n ; ++ i ) 
    {
        cnt[ i ] = 1 ; 
    }
    for ( int i = 1 ; i <= n ; ++ i ) 
    {
        cnt[ i ] = -1 ; 
    }
    for ( int i = 2 * n ; i >= 1 ; -- i ) 
    {
        if ( Next[ i ] != i ) 
        {
            cnt[ Next[ i ] ] += cnt[ i ] ; 
            cnt[ i / Next[ i ] ] += cnt[ i ] ; 
            cnt[ i ] = 0 ;  
        }
    }
    int ans = 1 ; 
    for ( int i = 1 ; i <= 2 * n ; ++ i ) 
    {
        ans = ( ans * Regular_Quick_Pow( i , cnt[ i ] ) ) % mod ;  
    }
    cout << ans ; 
} 

相關文章