组合数取模的几种方法--Exlucas&杨辉三角&组合

组合数取模的几个方法

求:

\[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 ; 
} 
posted @ 2024-03-14 08:14  HANGRY_Sol&Cekas  阅读(31)  评论(3编辑  收藏  举报