组合数取模的几种方法--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 ;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通