组合数的计算方法 1
方法 (1):考虑用 (operatorname{DP}) 求解,设 (f_{i,j}) 表示 (i choose j) 那么可以得出 (operatorname{DP}) 方程为 (f_{i,j}=f_{i-1,j-1}+f_{i-1,j})(其中 (f_{i-1,j-1}) 表示新增加的这个数要选,(f_{i-1,j}) 表示新增加的数不选)。
预处理的时间复杂度 (mathcal{O}(n^2)),单次查询的复杂度为 (mathcal{O}(1))。
因为方法 (1) 的转移中只有简单的加减,所以对模数并没有什么限制,但是复杂度略高,难以处理数据较大时的情况。
组合数的计算方法 2
方法 (2):考虑预处理出阶乘以及阶乘的逆元,这样可以直接使用 ({n choose m}=frac{n!}{m!(n-m)!}) 得到答案。
预处理的时间复杂度可以做到 (mathcal{O}(n)),单次查询的复杂度为 (mathcal{O}(1))。
因为方法 (2) 需要处理逆元,所以必须满足逆元的存在。
组合数的计算方法 3
方法 (3):当 (n,m) 非常大时,考虑用 (mathcal{L}ucas) 求解,下面介绍一下什么是 (mathcal{L}ucas) 定理。
先介绍一个辅助工具 extbf{多项式下的同余}:
如果两个存在两个多项式 (f=sumlimits_{i=0}^{n}a_ix^i,g=sumlimits_{i=0}^{n}b_ix^i) 满足对于任意的 (iin[0,n]) 都有 (a_iequiv b_ipmod p),那么就称 (f) 和 (g) 在模 (p) 意义下同余,即 (fequiv gpmod p)。
除此之外,还需要知道一个结论:
如果 (p) 是质数,那么对于任意的 (iin(0,p)) 都有 ({pchoose i}equiv 0pmod p)。
所以可以得出:
(其中 (p) 是质数)
现在我们要计算 ((1+x)^n),先把 (n) 转成 (p) 进制(假设 (n) 在 (p) 进制下位 (n_0n_1n_2dots),即 (n=sum n_ip^i)),因为
所以可以得出
可以发现这些部分的卷积在任何一个位置恰好只有在一个位置对答案产生影响,即在 (m) 在 (p) 进制下的对应位置有贡献,所以可以得到
(假设 (m) 在 (p) 进制下位 (m_0m_1m_2dots))
预处理复杂度为 (mathcal{O}(p)),单次查询的复杂度为 (mathcal{O}(log_{(p)}n))。
方法 (3) 需要处理出 (0~p-1) 的数的阶乘,所以通常情况下 (p) 不可以很大。
组合数的计算方法 4
方法 (4):考虑方法 (2) 预处理的复杂度至少为 (mathcal{O}(n)),如果 (n,m) 比较大时((n,m) 在 (10^9) 级别左右),(mathcal{O}(n)) 的复杂度就难以接受。可以发现方法 (2) 的瓶颈在于计算阶乘,于是有了一种非常暴力的处理方法:
考虑先利用另一个程序计算出 (10^9) 内的阶乘,设定一个阈值 (W),将 (W) 的倍数的阶乘放入一个数组中((W) 大约在 (10^5sim 10^6) 内比较适合),这样我们可以得到一份大小可以接受的表,那么计算一个阶乘只需要找到最接近的一个在数组中的数再继续往上乘,那么单次计算的复杂度上界为 (mathcal{O}(W))。我们称这种方法为分块打表。
方法 (4) 同样也存在着方法 (2) 的弊病,而且模数不可以改变,单次计算复杂度较高(如果想要改变模数可以使用快速阶乘算法)。
组合数的计算方法 5
方法 (5):考虑到普通的 (mathcal{L}ucas) 的预处理的复杂度至少为 (mathcal{O}(p)),但瓶颈也在于计算阶乘,自然也是可以用分块打表来解决。
这样做单次查询的复杂度为 (mathcal{O}(Wlog_{(p)}n)),因为 (p) 一般很大,所以其实并不会很慢。
这样我们就得到一个可以计算 (n,m) 在 (10^{18}) 级别,模数在 (10^9) 级别的计算组合数的优秀做法。
组合数的计算方法 6
方法 (6):考虑最暴力的任意模数做法,因为要任意模数所以不能存在模意义下的除法,考虑直接对每个数分解质因数,利用欧拉筛晒出最小质因子后可以做到 (mathcal{O}(nlog n)) 的复杂度完成对于 (1sim n) 中的每一个质数出现次数的统计。
不过这样计算的复杂度还是有点高了,这里给出一种更优的做法。
考虑到 (1sim n) 中的素数个数为 (mathcal{O}(frac{n}{log n})) 级别。所以可以只枚举每个 (1sim n) 中的质数再计算每种质数的贡献。
至于计算每个质数 (p) 的贡献显然可以轻松得到:
((num) 为 (p) 这个质数在 (1sim n) 质因数分解后出现次数)
最后计算所有质数的贡献的乘积时只需要快速幂即可,经过简单分析可以得出这个做法的复杂度上界是 (mathcal{O}(n)) 的。
组合数的计算方法 7
方法 (7):当 (n,m) 很大且 (p) 不一定是质数,就不能简单套用的 (mathcal{L}ucas)。于是可以考虑用 (operatorname{Ex}mathcal{L}ucas) 解决。下面介绍一下 (operatorname{Ex}mathcal{L}ucas)。
因为 (p) 不一定是质数,所以考虑先对 (p) 质因数分解((p) 质因数分解后 (p_0^{k_0}p_1^{k_1}dots))
现在要计算 ({nchoose m}mod p),显然可以先计算出所有的 ({nchoose m}mod {p_i^{k_i}})(因为对于每个 (i) 的计算方法都一样,所以下面称 (p_i) 为 (p),(k_i) 为 (k)),再用 (operatorname{CRT}) 合并即可。
还是回归本源 ({nchoose m}=frac{n!}{m!(n-m)!}),所以现在要求的是 (frac{n!}{m!(n-m)!}mod p^x),因为要计算除法,但是除数与模数不互质的情况下是不存在逆元的,所以可以考虑先将除数和被除数中的 (p) 都提出。
设 (F_p(n)) 表示 (frac{n!}{p^x})(其中 (x) 为满足 (F_p(n)) 为自然数时最大的自然数),用 (G_p(n)) 表示这里的 (x)。也就是说 (n!=F_p(n) imes p^{G_p(n)})。
可以得出:
并且根据定义可以得出 (F_p(n) otequiv 0pmod p),因为 (p) 这里是质数,所以可以得到 (p) 与 (F_p(n)) 互质,(p^k) 自然也和 (F_p(n)) 互质。这样我们就可以愉快地去计算逆元啦。
下面来考虑 (F_p(n)) 和 (G_p(n)) 如何计算:
先考虑 (G_p(n)),可以发现这个东西在方法 (6) 中已经计算过了:
再来考虑 (F_p(n)) 的计算:
先把 (1~n) 中 (p) 的倍数拎出来不考虑(下面假设 (p=5)),那么剩下部分为:
把每 (4) 个分为一组,那么每一组都可以都表示为 ((5k+1) imes(5k+2) imes(5k+3) imes(5k+4) (kin mathbb{Z}_*)),那么显然对于每一组都是在模 (5) 意义下同余的。
所以我们可以得到:
可以发现提出的数都是 (5k (kin mathbb{Z}_*)),把 (5) 除掉后又变成了 (1,2,3,dots),于是又变成了相同的问题,所以可以直接递归求解。
综合以上内容可以得出以下式子:
但是这里要求的是 (F_p(n)mod p^k),所以需要将上面得到的这个式子稍微变形以下得到下面式子:
其中 (R_p(n)) 是在阶乘中除去 (p) 的倍数,即 (frac{n!}{prod_{i=1}^{lfloorfrac{n}{p} floor}pi}),可以在预处理的时候直接处理出来。
可能不能一眼看出这连个式子的不同之处,但是在计算 (F_2(3)mod 2^2) 时就可以发现如果直接套用第一个式子会得到的是 (1) 而不是 (3)。
需要用到 (operatorname{Ex}mathcal{L}ucas) 的题目一般都会对 (max{p_i^{k_i}}) 的范围有规定,因为需要处理出所有的 (p_i^{k_i}) 以内的阶乘模 (p_i^{k_i}) 下的结果。
预处理的复杂度 (mathcal{O}(sum p_i^{k_i})),单次询问的时间复杂度 (mathcal{O}(log^2n)),只是不知道为什么跑起来非常慢。