卢卡斯定理是一个与组合数有关的数论定理,在算法竞赛中用于求组合数对某质数的模。

第一部分是博主的个人理解,第二部分为 Pecco 学长的介绍

第一部分

一般情况下,我们计算大组合数取模问题是用递推公式进行计算的:

\[C_n^m=(C_{n-1}^m+C_{n-1}^{m-1}) mod\ p
\]

其中p相对较小的素数。但是当n和m过大时,计算的耗费就急剧增加\(O(mn)\),在实践中不适用。当这时候就需要Lucas定理进行快速运算:

\[C_n^m=\prod_{i=0}^{k}C_{n_i}^{m_i}\ mod\ p
\]

其中:

\[m=m_kp^k+m_{k-1}p^{k-1}+...+m_1p+m_0
\]
\[n=n_kp^k+n_{k-1}p^{k-1}+...+n_1p+n_0
\]

证明方法也很简单,主要用到如下等式:

\[C_p^j\equiv 0\ mod\ p\ ( 1 \leq j \leq p-1 )
\]
\[(1+x)^{p}\equiv 1+x^p \ mod\ p
\]

应用这个公式,可以的到如下递归式

这里的\(Lucas(n,m,p)\)就是\(C_n^m\ mod\ p\),递归终点就是当\(n=0\)的时候。时间复杂度是\(O(log_p(n)*p)\).


如果上面的解释没有理解的话请往下看一下 Pecco 学长的介绍

第二部分

开篇说的就很清楚了,

卢卡斯定理是一个与组合数有关的数论定理,在算法竞赛中用于求组合数对某质数的模。

在我们谈论卢卡斯定理前,我们先来看看朴素的求组合数的方法有哪些。

如果直接根据定义 \(C_m^n = \frac{m!}{n!(m-n)!}\) 直接计算,显然很容易溢出,事实上当 m=21 时,\(21! = 51,090,942,171,709,440,000\) 就已经大于64位整数可以表示的范围了。当然我们可以边乘边除,但有点麻烦。于是我们有另外一种思路,利用递推式:\(C_m^n =C_{m-1}^{n - 1} + C_{m - 1}^n\) (这个递推式可以从杨辉三角看出)。这种方法相对不容易溢出,时间复杂度为 \(\mathcal{O}(n^2)\) 其实如果对精度要求不高的话,最简单快捷的方法是利用对数。由于 :

\[ln C_m^n = ln\ m! - ln\ n! - ln(m - n)! = \sum_{x=1}^mlnx - \sum_{x=1}^nlnx - \sum_{x=1}^{m-n}lnx
\]

所以只需要用 \(\mathcal{O}(n)\) 预处理出 \(ln\ x\) 的前缀和,即可 \(\mathcal{O}(1)\) 求出结果,但可能有浮点误差。

然而,实际上,组合数的增长速度是非常快的,\(C_{100}^{50}\) 已经是30位数,\(C_{300}^{150}\) 则有89位数,比宇宙中的原子数还多。(宇宙中的原子数:怎么总是拿我来对比?)所谓递推不容易溢出,那如果结果本身就溢出了,你又怎么办呢?

所幸算法竞赛中的题目常常会要求将结果对某个质数 \(p\) 取模,这样一来,溢出的问题就不用太担心了。我们干脆直接回到最原始的方法:\(C_m^n=\frac{m!}{n!(m-n)!}\)。只不过,现在我们要把除法变成求 逆元,也即:\(C_m^n = m!·inv(n!)·inv[(m - n)!]\ (mod\ p)\)。

模 \(p\) 意义下阶乘和逆元都可以 \(\mathcal{O}(n)\)预 处理出来,然后直接 \(\mathcal{O}(1)\) 查询即可(实际上不预处理逆元直接 \(\mathcal{O}(log\ n)\) 求也绰绰有余)。这基本上是最常用的求组合数方法。

绕了一圈,怎么还没提到卢卡斯定理呢?嗯……一般来说,这个方法够用了。偏偏,有时候, \(p\) 可能比 \(m\) 小....

这下麻烦了。如果 \(p\) 比 \(m\) 小,就不能保证 \(n\) 和 \(m - n\) 的逆元存在了(它们可能是 \(p\) 的倍数)。当然还是可以用杨辉三角递推,但 \(\mathcal{O}(n^2)\)还是太不理 想。于是,本文的主角——卢卡斯定理终于要出场了。

卢卡斯定理(Lucas's theorem):

对于非负整数 \(m,n\) 和质数 \(p\) ,\(C_m^n = \prod_{i = 0}^k\ (mod\ p)\) 其中

\(m = m_kp^k + …… +m_1p + m_0\) 、\(n = n_kp^k + …… +n_1p + n_0\) 是 \(m\) 和 \(n\) 的 \(p\) 进制展开

但其实,我们一般使用的是这个可以与之互推的式子:

\(C_m^n = C_{m\ mod\ p}^{n\ mod\ p}·C_{\lfloor\frac{m}{p}\rfloor}^{\lfloor\frac{n}{p}\rfloor}\ (mod\ p)\)

当 \(m < n\) 时,规定 \(C_m^n = 0\) (待会儿会将这个规定的意义)。

就像辗转相除法那样,可以利用这个式子递归求解,递归出口是 \(n = 0\) 。其实这篇文章只需要这个好记的公式就够了,你甚至可以马上写出卢卡斯定理的板子:

// 需要先预处理出fact[],即阶乘
inline ll C(ll m, ll n, ll p) {
return m < n ? 0 : fact[m] * inv(fact[n], p) % p * inv(fact[m - n], p) % p;
}
inline ll lucas(ll m, ll n, ll p) {
return n == 0 ? 1 % p : lucas(m / p, n / p, p) * C(m % p, n % p, p) % p;
}

网上说卢卡斯定理的复杂度是 \(\mathcal{O}(p\ log_p\ m)\) ,但如果阶乘和逆元都采取递推的方法预处理,(只需要预处理 \(p\) 以内的),每次调用C()函数应该都是 的\(\mathcal{O}(1)\),一共要调用 次,那么复杂度应该是 \(\mathcal{O}(p + log_p\ m)\) 才对。洛谷上这道模板题的范围才给到 \(\mathcal{O}(10^5)\) ,屈才了。

接下来我们来证明这个式子。如果你对数学推导没有兴趣可以走了(雾

【算法学习笔记】组合数与 Lucas 定理的更多相关文章

  1. 某科学的PID算法学习笔记

    最近,在某社团的要求下,自学了PID算法.学完后,深切地感受到PID算法之强大.PID算法应用广泛,比如加热器.平衡车.无人机等等,是自动控制理论中比较容易理解但十分重要的算法. 下面是博主学习过程中 ...

  2. C / C++算法学习笔记(8)-SHELL排序

    原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...

  3. Manacher算法学习笔记 | LeetCode#5

    Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...

  4. Johnson算法学习笔记

    \(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...

  5. Johnson 全源最短路径算法学习笔记

    Johnson 全源最短路径算法学习笔记 如果你希望得到带互动的极简文字体验,请点这里 我们来学习johnson Johnson 算法是一种在边加权有向图中找到所有顶点对之间最短路径的方法.它允许一些 ...

  6. 二次剩余Cipolla算法学习笔记

    对于同余式 \[x^2 \equiv n \pmod p\] 若对于给定的\(n, P\),存在\(x\)满足上面的式子,则乘\(n\)在模\(p\)意义下是二次剩余,否则为非二次剩余 我们需要计算的 ...

  7. 算法学习笔记——sort 和 qsort 提供的快速排序

    这里存放的是笔者在学习算法和数据结构时相关的学习笔记,记录了笔者通过网络和书籍资料中学习到的知识点和技巧,在供自己学习和反思的同时为有需要的人提供一定的思路和帮助. 从排序开始 基本的排序算法包括冒泡 ...

  8. 【学习笔记】扩展卢卡斯定理 exLucas

    引子 求 \[C_n^m\ \text{mod}\ p \] 不保证 \(p\) 是质数. 正文 对于传统的 Lucas 定理,必须要求 \(p\) 是质数才行.若 \(p\) 不一定是质数,则需要扩 ...

  9. [UOJ 275/BZOJ4737] 【清华集训2016】组合数问题 (LUCAS定理的运用+数位DP)

    题面 传送门:UOJ Solution 这题的数位DP好蛋疼啊qwq 好吧,我们说回正题. 首先,我们先回忆一下LUCAS定理: \(C_n^m \equiv C_{n/p}^{m/p} \times ...

随机推荐

  1. js 位掩码

    原文 定义掩码 const mask0 = parseInt("00000001", 2); const mask1 = parseInt("00000010" ...

  2. java中synchronized与Lock的异同

    本文转载自java中synchronized与Lock的异同 前言 synchronized和Lock通过互斥保障原子性,能够保护共享数据以实现线程安全,其作用包括保障原子性.可见性.有序性 常见问题 ...

  3. Java魔法类:Unsafe应用解析

    本文转载自Java魔法类:Unsafe应用解析 前言 Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别.不安全操作的方法,如直接访问系统内存资源.自主管理内存资源等,这些方法 ...

  4. SQL EXPLAIN解析

    本文转载自MySQL性能优化最佳实践 - 08 SQL EXPLAIN解析 什么是归并排序? 如果需要排序的数据超过了sort_buffer_size的大小,说明无法在内存中完成排序,就需要写到临时文 ...

  5. DRF 外键字段深度查询优化、ListSerializer辅助完成群改

    目录 一.Response封装 二.外键字段深度查询 1.序列化配置exclude.depth 2.模型层函数.插拔式字段查询 三.listserializer辅助类 一.Response封装 用de ...

  6. 看完我的笔记不懂也会懂----less

    目录 Less学习 语法篇 注释 变量 映射(Maps) @规则嵌套和冒泡 less中的嵌套规则 less中的混合 less的运算 extend延伸/继承 less忽略编译(转义) 导入(Import ...

  7. CentOS rpm常用功能记录

    CentOS7主要有rpm和yum这两种包软件的管理.两者有功能上的区别,其中主要区别是:yum使用简单但需要联网,yum会去网上包源去获取所需要的软件包.而rpm的需要做的事情就更细一些,比如我们需 ...

  8. Linux操作php.ini文件

    有时你使用的是别人搭建好的环境,不知道php.ini在哪里,或者好久没有修改配置了,已经忘记了路径在哪,所以在操作文件之前,得先要找到.ini路径. 找php.ini 方式一 $ php -i | g ...

  9. 剑指 Offer 39. 数组中出现次数超过一半的数字 + 摩尔投票法

    剑指 Offer 39. 数组中出现次数超过一半的数字 Offer_39 题目描述 方法一:使用map存储数字出现的次数 public class Offer_39 { public int majo ...

  10. JAVA中枚举Enum详解

    1.关键字:enum.枚举可以定义成单独的文件,也可以定义在其他类内部. 枚举在类内部的示例: public class EnumInner { public static void main(Str ...