「ExLucas」学习笔记

前置芝士

  • 中国剩余定理 \(CRT\)

  • \(Lucas\) 定理

  • \(ExGCD\)

  • 亿点点数学知识

给龙蝶打波广告

Lucas 定理

\(C^m_n = C^{m\% mod}_{n\% mod} \times C^{\frac{m}{mod}}_{\frac{n}{mod}}\)

适用条件

  • 给出的数据范围较大(无法用线性求出)

  • 模数很烂的时候(会使阶乘中出现 \(0\))

  • \(mod\) 必须为质数

证明

证明很恶心,略。

模板

某谷P4720

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. #define int long long
  6. #define DEBUG puts ("emmmm");
  7. using namespace std;
  8. const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
  9. inline int read () {
  10. register int x = 0, w = 1;
  11. char ch = getchar();
  12. for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
  13. for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
  14. return x * w;
  15. }
  16. int T, n, m, mod;
  17. int jc[maxn];
  18. inline void Init () {
  19. jc[1] = 1;
  20. for (register int i = 2; i <= n + m; i ++) {
  21. jc[i] = jc[i - 1] * i % mod;
  22. }
  23. }
  24. inline int qpow (register int a, register int b) {
  25. register int ans = 1;
  26. while (b) {
  27. if (b & 1) ans = ans * a % mod;
  28. a = a * a % mod;
  29. b >>= 1;
  30. }
  31. return ans;
  32. }
  33. inline int C (register int a, register int b) {
  34. if (a == 0 || b == 0 || a == b) return 1;
  35. if (a < b) return 0;
  36. return jc[a] * qpow (jc[a - b], mod - 2) % mod * qpow (jc[b], mod - 2) % mod;
  37. }
  38. inline int Lucas (register int a, register int b) {
  39. if (a == 0 || b == 0) return 1;
  40. return C (a % mod, b % mod) * Lucas (a / mod, b / mod) % mod;
  41. }
  42. signed main () {
  43. T = read();
  44. while (T --) {
  45. n = read(), m = read(), mod = read();
  46. Init ();
  47. printf ("%lld\n", Lucas (n + m, n));
  48. }
  49. return 0;
  50. }

扩展 Lucas

若题目中给出的 \(mod\) 不能保证是质数,当我们在求的时候,还是会出现 \(0\) 的情况,\(ExLuacs\) 就是来解决这种问题的。

STEP1

对于一个非质数 \(p\),我们可以将其进行质因数分解,化成 \(\prod_ip_i^{k_i}\) 的形式。

我们就可以将原式子 \(C^m_n(mod \; p)\) 化成若干个同余方程:

\(\left\{\begin{matrix}
C^m_n \equiv b_1 (mod \; p_1^{k_1})\\
C^m_n \equiv b_2 (mod \; p_2^{k_2})\\
C^m_n \equiv b_3 (mod \; p_3^{k_3})\\
......\\
C^m_n \equiv b_i (mod \; p_i^{k_i})
\end{matrix}\right.\)

这样最后用 \(CRT\) 求出 \(C^m_n\) 即可。

STEP2

  • 现在问题变成了如何求每个 \(b_i\) 。

\(b_i = C^m_n (mod \; p_i ^ {k_i}) = \frac{n!}{m! \times (n - m)!} (mod \; p_i ^ {k_i})\)

但是我们会发现 \(p_i ^ {k_i}\) 仍不是质数, \(m!\) 和 \((n - m)!\) 的逆元仍求不出来。

所以我们将 \(n!\) 和 \(m!\) 和 \((n - m)!\) 中的所有质因子 \(p_i\) 都提出来,化成:

\(\frac{\frac{n!}{p_i^{k_1}}}{\frac{m!}{p_i^{k_2}} \times \frac{(n - m)!}{p_i^{k_3}}} \times p_i^{k_1-k_2-k_3}\)

这样分母上的就可以求出逆元了。

STEP3

  • 现在问题变成了如何求每个 \(\frac{n!}{p_i^{k_1}}\)

举个栗子!!

例如 \(n=22,p=3,k=2\)

\(n!=22\times 21\times 20\times 19\times 18\times 17\times 16\times 15\times 14\times 13\times 12\times 11\times 10\times 9\times 8\times 7\times 6\times 5\times 4\times 3\times 2\times 1\)

\(=3^7\times(1\times 2\times 3\times 4\times 5\times 6\times 7) \times (1\times 2\times 4\times 5\times 7\times 8)\times (10\times 11\times 13\times 14\times 16\times 17)\times (19\times 20\times 22)\)

我们会发现这个式子由三部分组成:

  • \(3^7\) 为 \(p^{\frac{n!}{p}}\)

  • \(7!\) 可以继续递归下去求解

  • 可以看出是在 \((mod \; 9)\) 意义下是一个循环节,长度为 \(\frac{n}{p_i^{k_i}}\),类似 \(19\times 20\times 22\) 这样剩下的直接暴力求即可。

但是我们会发现第一部分会被原式子的分母消掉,所以不用计算,对于剩下的包含质因子 \(p_i\) 的,直接不计算即可。

  1. inline int Calc (register int n, register int p, register int pk) {
  2. if (n == 0) return 1;
  3. register int ans = 1;
  4. for (register int i = 1; i <= pk; i ++) { // 每个循环节
  5. if (i % p) ans = ans * i % pk;
  6. }
  7. ans = qpow (ans, n / pk, pk); // 计算所有的循环节
  8. for (register int i = 1; i <= n % pk; i ++) { // 乘下剩下的
  9. if (i % p) ans = ans * i % pk;
  10. }
  11. return ans * Calc (n / p, p, pk) % pk;
  12. }

最后

现在我们已经将所有要用的东西都求出来了,最后直接倒着退回去即可。

代码

某谷P4720

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. #include <cmath>
  6. #define int long long
  7. #define DEBUG puts ("emmmm")
  8. const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
  9. using namespace std;
  10. inline int read () {
  11. register int x = 0, w = 1;
  12. char ch = getchar ();
  13. for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
  14. for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
  15. return x * w;
  16. }
  17. int n, m, p, tot;
  18. int b[maxn], c[maxn], d[maxn];
  19. inline int qpow (register int a, register int b, register int mod) {
  20. register int ans = 1;
  21. while (b) {
  22. if (b & 1) ans = ans * a % mod;
  23. a = a * a % mod;
  24. b >>= 1;
  25. }
  26. return ans;
  27. }
  28. inline int ExGCD (register int a, register int b, register int &x, register int &y) {
  29. if (b == 0) {
  30. x = 1, y = 0;
  31. return a;
  32. }
  33. register int d = ExGCD (b, a % b, x, y);
  34. register int tmp = x;
  35. x = y;
  36. y = tmp - (a / b) * y;
  37. return d;
  38. }
  39. inline int Inv (register int a, register int mod) { // 利用扩展欧几里德求逆元
  40. register int x = 0, y = 0;
  41. ExGCD (a, mod, x, y);
  42. return (x % mod + mod) % mod;
  43. }
  44. inline int Calc (register int n, register int p, register int pk) {
  45. if (n == 0) return 1;
  46. register int ans = 1;
  47. for (register int i = 1; i <= pk; i ++) { // 每个循环节
  48. if (i % p) ans = ans * i % pk;
  49. }
  50. ans = qpow (ans, n / pk, pk); // 计算所有的循环节
  51. for (register int i = 1; i <= n % pk; i ++) { // 乘下剩下的
  52. if (i % p) ans = ans * i % pk;
  53. }
  54. return ans * Calc (n / p, p, pk) % pk;
  55. }
  56. inline int C (register int n, register int m, register int p, register int pk) {
  57. if (n == 0 || m == 0 || n == m) return 1;
  58. if (n < m) return 0;
  59. register int nn = Calc (n, p, pk), mm = Calc (m, p, pk), nm = Calc (n - m, p, pk), cnt = 0, k = n - m;
  60. while (n) n /= p, cnt += n;
  61. while (m) m /= p, cnt -= m;
  62. while (k) k /= p, cnt -= k;
  63. return nn * Inv (mm, pk) % pk * Inv (nm, pk) % pk * qpow (p, cnt, pk) % pk;
  64. }
  65. inline int CRT () { // 中国剩余定理
  66. register int M = 1, ans = 0;
  67. for (register int i = 1; i <= tot; i ++) {
  68. M *= c[i];
  69. }
  70. for (register int i = 1; i <= tot; i ++) {
  71. d[i] = Inv (M / c[i], c[i]);
  72. }
  73. for (register int i = 1; i <= tot; i ++) {
  74. ans += b[i] * (M / c[i]) * d[i];
  75. }
  76. return (ans % M + M) % M;
  77. }
  78. inline void ExLucas (register int n, register int m, register int p) {
  79. register int tmp = sqrt (p);
  80. for (register int i = 2; i <= tmp && p >= 1; i ++) { // 将p拆分质因数
  81. register int pk = 1;
  82. while (p % i == 0) p /= i, pk *= i;
  83. if (pk > 1) {
  84. b[++ tot] = C (n, m, i, pk), c[tot] = pk;
  85. }
  86. }
  87. if (p > 1) b[++ tot] = C (n, m, p, p), c[tot] = p;
  88. printf ("%lld\n", CRT ());
  89. }
  90. signed main () {
  91. n = read(), m = read(), p = read();
  92. ExLucas (n, m, p);
  93. return 0;
  94. }

例题

[国家集训队]礼物

某谷P2183

思路很简单,就是没取一个 \(w[i]\),总数就得减小,依次用 \(ExLucas\) 求组合数即可。

代码

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. #include <cmath>
  6. #define int long long
  7. #define DEBUG puts ("emmmm")
  8. const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
  9. using namespace std;
  10. inline int read () {
  11. register int x = 0, w = 1;
  12. char ch = getchar ();
  13. for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
  14. for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
  15. return x * w;
  16. }
  17. int n, m, p, totw, tot, ans = 1;
  18. int w[maxn];
  19. int b[maxn], c[maxn], d[maxn];
  20. inline void Init () {
  21. memset (b, 0, sizeof b);
  22. memset (c, 0, sizeof c);
  23. memset (d, 0, sizeof d);
  24. tot = 0;
  25. }
  26. inline int qpow (register int a, register int b, register int mod) {
  27. register int ans = 1;
  28. while (b) {
  29. if (b & 1) ans = ans * a % mod;
  30. a = a * a % mod;
  31. b >>= 1;
  32. }
  33. return ans;
  34. }
  35. inline int ExGCD (register int a, register int b, register int &x, register int &y) {
  36. if (b == 0) {
  37. x = 1, y = 0;
  38. return a;
  39. }
  40. register int d = ExGCD (b, a % b, x, y);
  41. register int tmp = x;
  42. x = y;
  43. y = tmp - (a / b) * y;
  44. return d;
  45. }
  46. inline int Inv (register int a, register int mod) {
  47. register int x = 0, y = 0;
  48. ExGCD (a, mod, x, y);
  49. return (x % mod + mod) % mod;
  50. }
  51. inline int Calc (register int n, register int p, register int pk) {
  52. if (n == 0) return 1;
  53. register int ans = 1;
  54. for (register int i = 1; i <= pk; i ++) {
  55. if (i % p) ans = ans * i % pk;
  56. }
  57. ans = qpow (ans, n / pk, pk);
  58. for (register int i = 1; i <= n % pk; i ++) {
  59. if (i % p) ans = ans * i % pk;
  60. }
  61. return ans * Calc (n / p, p, pk) % pk;
  62. }
  63. inline int C (register int n, register int m, register int p, register int pk) {
  64. if (n == 0 || m == 0 || n == m) return 1;
  65. if (n < m) return 0;
  66. register int nn = Calc (n, p, pk), mm = Calc (m, p, pk), nm = Calc (n - m, p, pk), cnt = 0, k = n - m;
  67. while (n) n /= p, cnt += n;
  68. while (m) m /= p, cnt -= m;
  69. while (k) k /= p, cnt -= k;
  70. return nn * Inv (mm, pk) % pk * Inv (nm, pk) % pk * qpow (p, cnt, pk) % pk;
  71. }
  72. inline int CRT () {
  73. register int M = 1, ans = 0;
  74. for (register int i = 1; i <= tot; i ++) {
  75. M *= c[i];
  76. }
  77. for (register int i = 1; i <= tot; i ++) {
  78. d[i] = Inv (M / c[i], c[i]);
  79. }
  80. for (register int i = 1; i <= tot; i ++) {
  81. ans += b[i] * (M / c[i]) * d[i];
  82. }
  83. return (ans % M + M) % M;
  84. }
  85. inline int ExLucas (register int n, register int m, register int p) {
  86. Init ();
  87. register int tmp = sqrt (p);
  88. for (register int i = 2; i <= tmp && p > 1; i ++) {
  89. register int pk = 1;
  90. while (p % i == 0) p /= i, pk *= i;
  91. b[++ tot] = C (n, m, i, pk);
  92. c[tot] = pk;
  93. }
  94. if (p > 1) {
  95. b[++ tot] = C (n, m, p, p);
  96. c[tot] = p;
  97. }
  98. return CRT ();
  99. }
  100. signed main () {
  101. p = read(), n = read(), m = read();
  102. for (register int i = 1; i <= m; i ++) {
  103. w[i] = read();
  104. totw += w[i];
  105. }
  106. if (totw > n) {
  107. puts ("Impossible");
  108. } else {
  109. register int sum = n;
  110. for (register int i = 1; i <= m; i ++) {
  111. ans = (ans * ExLucas (sum, w[i], p)) % p;
  112. sum -= w[i];
  113. }
  114. printf ("%lld\n", ans);
  115. }
  116. return 0;
  117. }

「ExLucas」学习笔记的更多相关文章

  1. Note -「群论」学习笔记

    目录 前置知识 群 置换 Burnside 引理与 Pólya 定理 概念引入 引例 轨道-稳定子(Orbit-Stabilizer)定理 证明 Burnside 引理 证明 Pólya 定理 证明 ...

  2. Note -「线性规划」学习笔记

    \(\mathcal{Definition}\)   线性规划(Linear Programming, LP)形式上是对如下问题的描述: \[\operatorname{maximize}~~~~z= ...

  3. 【Java】「深入理解Java虚拟机」学习笔记(1) - Java语言发展趋势

    0.前言 从这篇随笔开始记录Java虚拟机的内容,以前只是对Java的应用,聚焦的是业务,了解的只是语言层面,现在想深入学习一下. 对JVM的学习肯定不是看一遍书就能掌握的,在今后的学习和实践中如果有 ...

  4. Note -「Lagrange 插值」学习笔记

    目录 问题引入 思考 Lagrange 插值法 插值过程 代码实现 实际应用 「洛谷 P4781」「模板」拉格朗日插值 「洛谷 P4463」calc 题意简述 数据规模 Solution Step 1 ...

  5. Note -「动态 DP」学习笔记

    目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...

  6. Note -「圆方树」学习笔记

    目录 圆方树的定义 圆方树的构造 实现 细节 圆方树的运用 「BZOJ 3331」压力 「洛谷 P4320」道路相遇 「APIO 2018」「洛谷 P4630」铁人两项 「CF 487E」Touris ...

  7. Note -「Dsu On Tree」学习笔记

    前置芝士 树连剖分及其思想,以及优化时间复杂度的原理. 讲个笑话这个东西其实和 Dsu(并查集)没什么关系. 算法本身 Dsu On Tree,一下简称 DOT,常用于解决子树间的信息合并问题. 其实 ...

  8. Note -「单位根反演」学习笔记

    \(\mathcal{Preface}\)   单位根反演,顾名思义就是用单位根变换一类式子的形式.有关单位根的基本概念可见我的这篇博客. \(\mathcal{Formula}\)   单位根反演的 ...

  9. 「快速傅里叶变换(FFT)」学习笔记

    FFT即快速傅里叶变换,离散傅里叶变换及其逆变换的快速算法.在OI中用来优化多项式乘法. 本文主要目的是便于自己整理.复习 FFT的算法思路 已知两个多项式的系数表达式,要求其卷积的系数表达式. 先将 ...

随机推荐

  1. IDEA创建MAVEN项目并使用tomcat启动

    一.开发环境准备 1.JDK1.8,已经配置好环境变量 2.IDEA2019.2,目前稳定版里面个人认为还不错的 3.tomcat服务器,笔者使用的是apache-tomcat-8.5.57 4.使用 ...

  2. Python爬虫实战点触验证码, 模拟登陆bilibili

    爬虫思路如下: 利用自动化爬虫工具 Selenium 模拟点击输入等操作来进行登录 分析页面,获取点触验证码的点触图片,通过将图片发送给超级鹰打码平台识别后获取坐标信息 根据超级鹰返回的数据,模拟坐标 ...

  3. Labview学习之路(十一)日常编程技巧

    此文章用于记录自己在学习Labview过程中所用到的编程技巧,会一直更新下去. (一)移动控件 直接鼠标拖动. 按住shift键,鼠标移动,可以水平和竖直移动(取决于鼠标最开始的移动方向). 使用键盘 ...

  4. 记一次Layui分页

    <link rel="stylesheet" href="/layui/css/layui.css"> <div class="ro ...

  5. Android开发之ListView详解 以及简单的listView优化

    ListView列表视图 最常用的控件之一,使用场景例如:微信,手机QQ等等. android:divider:每个item之间的分割线,可以使用图片或者色值. android:dividerHeig ...

  6. css动画是否会被js阻塞

    css动画是否会被js阻塞 css的动画部分是会被js阻塞的,不过transform的动画则不会受影响. 下面举一个margin-left移动的动画下,启动js阻塞动画的性能图表 <style& ...

  7. Linux下mysql安装记录

    1.MySQL下载路径:https://dev.mysql.com/downloads/ Linux下的安装步骤:http://www.runoob.com/linux/mysql-install-s ...

  8. JS语法_类型

    类型 JS 的数据类型 boolean number string undefined null symbol object TS 额外的数据类型 void BigInt 是一种内置对象,它提供了一种 ...

  9. 如何在不使用OleDbCommandBuilder情况下使用OleDbDataAdapter更新Access数据库记录

    我在博客园的博问和微软论坛都曾经请教了这个问题(问题链接),可能我的问题太简单,并没有获得太多解答. 到今天为止,我自己通过查找和摸索,基本把这个问题解决了,还是记录下来,供其他朋友参考. 第一次解决 ...

  10. Java代码工具EasyCode使用

    写Java代码,增删改查,最无趣而又最基础.那机器人就来了,帮你写,减少你的基础的无趣的工作. 推荐两个代写代码的神奇工具Mybatis-generator与EasyCode.这两款软件的数据库持久层 ...