组合数算法
求组合数的两个公式
定义式 : C a b = A a b b ! 定义式: \quad C_a^b = \frac{A_a^b}{b!} 定义式:Cab=b!Aab
递推式 : C a b = C a − 1 b − 1 + C a − 1 b 递推式: \quad C_{a}^{b} = C_{a - 1}^{b - 1} + C_{a - 1}^{b} 递推式:Cab=Ca−1b−1+Ca−1b
等价式 : C a b = C a a − b 等价式: \quad C_{a}^{b} = C_{a}^{a - b} 等价式:Cab=Caa−b
必须满足: a >= b >= 0, 对于非法值 规定为0
, 最好对于会产生非法值的情况, 特殊处理!
比如: C[3][3] = C[2][2] + C[2][3]
, 其中C[2][3]
是非法值, 规定为0
比如: C[3][0] = C[2][-1] + C[2][0]
, 其中C[2][-1]
是非法值, 规定为0
对于C[0][0]
应该是多少呢? 他不是非法值;
由C[1][1] = C[0][0] + C[0][1] = 1
, 由于C[0][1]
是非法值 为0
故: C[0][0] = 1
定义式
若不在取模意义下
get_C( int _a, int _b){
_b = min( _b, _a - b);
ans = 1;
for( i = 1; i <= _b; ++i){
ans *= ( _a - _b + i);
ans /= i;
}
return ans;
}
- 由于
for
循环次数, 取决于b
的值, 故先让其取最小值 - 计算:
(a * (a-1) * (a-2) * ... * (a-b+1)) / (b * (b-1) * ... * 1) 如果先计算
a / b, 这可能不是整除; 而我们知道, 其结果一定是整数; 故, 要从
右侧开始, 这是可以整除的. 比如, 此时分母是
i, 则分子是
连续的i个数; 而
连续的i个数里, 一定有
i`的倍数
时间复杂度 : O ( b ) 时间复杂度: O(b) 时间复杂度:O(b)
若在取模意义下
TODO
递推式
可用于 取模 或 不取模
的情况均可.
通常适用在预处理数组
的情景下, 即通过递推式来统一预处理数组, 然后就可以O(1)
的查询结果
C[ N + 2][ M + 2];
init_C(){
for( i = 0; i <= N; ++i){
C[ i][ 0] = 1;
C[ i][ i] = 1;
for( int j = 1; j < i; ++j){
C[ i][ j] = C[ i - 1][ j - 1] + C[ i - 1][ j];
C[ i][ j] %= Mod_; //< 如果需要取模
}
}
}
对于[i][0]
和 [i][i]
, 必须要特殊处理, 对于会产生非法值的情况 要特殊处理:
[i][0]
的递推式中[i - 1][ -1]
是非法值[i][i]
的递推式中[i - 1][i]
是非法值
时间复杂度 : O ( n ∗ m ) , 且 O ( 1 ) 查询 时间复杂度: O(n * m), 且O(1)查询 时间复杂度:O(n∗m),且O(1)查询
例题分析
A = 1e6
, B = 50
, 我们会频繁的访问: C[ x + y][ x]
, 且x <= A, y <= B
乍一看, C[a][b]
, 两者都是A = 1e6
的量级, 看似是无法预处理的
但是, C[ x + y][ x] = C[ x + y][ y]
, 即C[ 1e6][ 50]
的量级!
等价式
转换在这里非常重要; 因此, 该问题是可以用预处理数组
方式的!
时间复杂度 : O ( 1 e 6 ∗ 50 ) 时间复杂度: O(1e6 * 50) 时间复杂度:O(1e6∗50)