「ExLucas」学习笔记
「ExLucas」学习笔记
前置芝士
中国剩余定理 \(CRT\)
\(Lucas\) 定理
\(ExGCD\)
亿点点数学知识
Lucas 定理
\(C^m_n = C^{m\% mod}_{n\% mod} \times C^{\frac{m}{mod}}_{\frac{n}{mod}}\)
适用条件
给出的数据范围较大(无法用线性求出)
模数很烂的时候(会使阶乘中出现 \(0\))
\(mod\) 必须为质数
证明
证明很恶心,略。
模板
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
#define DEBUG puts ("emmmm");
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
inline int read () {
register int x = 0, w = 1;
char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
int T, n, m, mod;
int jc[maxn];
inline void Init () {
jc[1] = 1;
for (register int i = 2; i <= n + m; i ++) {
jc[i] = jc[i - 1] * i % mod;
}
}
inline int qpow (register int a, register int b) {
register int ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
inline int C (register int a, register int b) {
if (a == 0 || b == 0 || a == b) return 1;
if (a < b) return 0;
return jc[a] * qpow (jc[a - b], mod - 2) % mod * qpow (jc[b], mod - 2) % mod;
}
inline int Lucas (register int a, register int b) {
if (a == 0 || b == 0) return 1;
return C (a % mod, b % mod) * Lucas (a / mod, b / mod) % mod;
}
signed main () {
T = read();
while (T --) {
n = read(), m = read(), mod = read();
Init ();
printf ("%lld\n", Lucas (n + m, n));
}
return 0;
}
扩展 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\) 的,直接不计算即可。
inline int Calc (register int n, register int p, register int pk) {
if (n == 0) return 1;
register int ans = 1;
for (register int i = 1; i <= pk; i ++) { // 每个循环节
if (i % p) ans = ans * i % pk;
}
ans = qpow (ans, n / pk, pk); // 计算所有的循环节
for (register int i = 1; i <= n % pk; i ++) { // 乘下剩下的
if (i % p) ans = ans * i % pk;
}
return ans * Calc (n / p, p, pk) % pk;
}
最后
现在我们已经将所有要用的东西都求出来了,最后直接倒着退回去即可。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define int long long
#define DEBUG puts ("emmmm")
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
using namespace std;
inline int read () {
register int x = 0, w = 1;
char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
int n, m, p, tot;
int b[maxn], c[maxn], d[maxn];
inline int qpow (register int a, register int b, register int mod) {
register int ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
inline int ExGCD (register int a, register int b, register int &x, register int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
register int d = ExGCD (b, a % b, x, y);
register int tmp = x;
x = y;
y = tmp - (a / b) * y;
return d;
}
inline int Inv (register int a, register int mod) { // 利用扩展欧几里德求逆元
register int x = 0, y = 0;
ExGCD (a, mod, x, y);
return (x % mod + mod) % mod;
}
inline int Calc (register int n, register int p, register int pk) {
if (n == 0) return 1;
register int ans = 1;
for (register int i = 1; i <= pk; i ++) { // 每个循环节
if (i % p) ans = ans * i % pk;
}
ans = qpow (ans, n / pk, pk); // 计算所有的循环节
for (register int i = 1; i <= n % pk; i ++) { // 乘下剩下的
if (i % p) ans = ans * i % pk;
}
return ans * Calc (n / p, p, pk) % pk;
}
inline int C (register int n, register int m, register int p, register int pk) {
if (n == 0 || m == 0 || n == m) return 1;
if (n < m) return 0;
register int nn = Calc (n, p, pk), mm = Calc (m, p, pk), nm = Calc (n - m, p, pk), cnt = 0, k = n - m;
while (n) n /= p, cnt += n;
while (m) m /= p, cnt -= m;
while (k) k /= p, cnt -= k;
return nn * Inv (mm, pk) % pk * Inv (nm, pk) % pk * qpow (p, cnt, pk) % pk;
}
inline int CRT () { // 中国剩余定理
register int M = 1, ans = 0;
for (register int i = 1; i <= tot; i ++) {
M *= c[i];
}
for (register int i = 1; i <= tot; i ++) {
d[i] = Inv (M / c[i], c[i]);
}
for (register int i = 1; i <= tot; i ++) {
ans += b[i] * (M / c[i]) * d[i];
}
return (ans % M + M) % M;
}
inline void ExLucas (register int n, register int m, register int p) {
register int tmp = sqrt (p);
for (register int i = 2; i <= tmp && p >= 1; i ++) { // 将p拆分质因数
register int pk = 1;
while (p % i == 0) p /= i, pk *= i;
if (pk > 1) {
b[++ tot] = C (n, m, i, pk), c[tot] = pk;
}
}
if (p > 1) b[++ tot] = C (n, m, p, p), c[tot] = p;
printf ("%lld\n", CRT ());
}
signed main () {
n = read(), m = read(), p = read();
ExLucas (n, m, p);
return 0;
}
例题
[国家集训队]礼物
思路很简单,就是没取一个 \(w[i]\),总数就得减小,依次用 \(ExLucas\) 求组合数即可。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define int long long
#define DEBUG puts ("emmmm")
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
using namespace std;
inline int read () {
register int x = 0, w = 1;
char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
int n, m, p, totw, tot, ans = 1;
int w[maxn];
int b[maxn], c[maxn], d[maxn];
inline void Init () {
memset (b, 0, sizeof b);
memset (c, 0, sizeof c);
memset (d, 0, sizeof d);
tot = 0;
}
inline int qpow (register int a, register int b, register int mod) {
register int ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
inline int ExGCD (register int a, register int b, register int &x, register int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
register int d = ExGCD (b, a % b, x, y);
register int tmp = x;
x = y;
y = tmp - (a / b) * y;
return d;
}
inline int Inv (register int a, register int mod) {
register int x = 0, y = 0;
ExGCD (a, mod, x, y);
return (x % mod + mod) % mod;
}
inline int Calc (register int n, register int p, register int pk) {
if (n == 0) return 1;
register int ans = 1;
for (register int i = 1; i <= pk; i ++) {
if (i % p) ans = ans * i % pk;
}
ans = qpow (ans, n / pk, pk);
for (register int i = 1; i <= n % pk; i ++) {
if (i % p) ans = ans * i % pk;
}
return ans * Calc (n / p, p, pk) % pk;
}
inline int C (register int n, register int m, register int p, register int pk) {
if (n == 0 || m == 0 || n == m) return 1;
if (n < m) return 0;
register int nn = Calc (n, p, pk), mm = Calc (m, p, pk), nm = Calc (n - m, p, pk), cnt = 0, k = n - m;
while (n) n /= p, cnt += n;
while (m) m /= p, cnt -= m;
while (k) k /= p, cnt -= k;
return nn * Inv (mm, pk) % pk * Inv (nm, pk) % pk * qpow (p, cnt, pk) % pk;
}
inline int CRT () {
register int M = 1, ans = 0;
for (register int i = 1; i <= tot; i ++) {
M *= c[i];
}
for (register int i = 1; i <= tot; i ++) {
d[i] = Inv (M / c[i], c[i]);
}
for (register int i = 1; i <= tot; i ++) {
ans += b[i] * (M / c[i]) * d[i];
}
return (ans % M + M) % M;
}
inline int ExLucas (register int n, register int m, register int p) {
Init ();
register int tmp = sqrt (p);
for (register int i = 2; i <= tmp && p > 1; i ++) {
register int pk = 1;
while (p % i == 0) p /= i, pk *= i;
b[++ tot] = C (n, m, i, pk);
c[tot] = pk;
}
if (p > 1) {
b[++ tot] = C (n, m, p, p);
c[tot] = p;
}
return CRT ();
}
signed main () {
p = read(), n = read(), m = read();
for (register int i = 1; i <= m; i ++) {
w[i] = read();
totw += w[i];
}
if (totw > n) {
puts ("Impossible");
} else {
register int sum = n;
for (register int i = 1; i <= m; i ++) {
ans = (ans * ExLucas (sum, w[i], p)) % p;
sum -= w[i];
}
printf ("%lld\n", ans);
}
return 0;
}
「ExLucas」学习笔记的更多相关文章
- Note -「群论」学习笔记
目录 前置知识 群 置换 Burnside 引理与 Pólya 定理 概念引入 引例 轨道-稳定子(Orbit-Stabilizer)定理 证明 Burnside 引理 证明 Pólya 定理 证明 ...
- Note -「线性规划」学习笔记
\(\mathcal{Definition}\) 线性规划(Linear Programming, LP)形式上是对如下问题的描述: \[\operatorname{maximize}~~~~z= ...
- 【Java】「深入理解Java虚拟机」学习笔记(1) - Java语言发展趋势
0.前言 从这篇随笔开始记录Java虚拟机的内容,以前只是对Java的应用,聚焦的是业务,了解的只是语言层面,现在想深入学习一下. 对JVM的学习肯定不是看一遍书就能掌握的,在今后的学习和实践中如果有 ...
- Note -「Lagrange 插值」学习笔记
目录 问题引入 思考 Lagrange 插值法 插值过程 代码实现 实际应用 「洛谷 P4781」「模板」拉格朗日插值 「洛谷 P4463」calc 题意简述 数据规模 Solution Step 1 ...
- Note -「动态 DP」学习笔记
目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...
- Note -「圆方树」学习笔记
目录 圆方树的定义 圆方树的构造 实现 细节 圆方树的运用 「BZOJ 3331」压力 「洛谷 P4320」道路相遇 「APIO 2018」「洛谷 P4630」铁人两项 「CF 487E」Touris ...
- Note -「Dsu On Tree」学习笔记
前置芝士 树连剖分及其思想,以及优化时间复杂度的原理. 讲个笑话这个东西其实和 Dsu(并查集)没什么关系. 算法本身 Dsu On Tree,一下简称 DOT,常用于解决子树间的信息合并问题. 其实 ...
- Note -「单位根反演」学习笔记
\(\mathcal{Preface}\) 单位根反演,顾名思义就是用单位根变换一类式子的形式.有关单位根的基本概念可见我的这篇博客. \(\mathcal{Formula}\) 单位根反演的 ...
- 「快速傅里叶变换(FFT)」学习笔记
FFT即快速傅里叶变换,离散傅里叶变换及其逆变换的快速算法.在OI中用来优化多项式乘法. 本文主要目的是便于自己整理.复习 FFT的算法思路 已知两个多项式的系数表达式,要求其卷积的系数表达式. 先将 ...
随机推荐
- Node.js 学习笔记之四:使用数据库
这部分示例将示范SQLite3.MongoDB这两种不同类型的数据库在 Node.js 中的使用方法.首先,我们要在code目录下执行mkdir 06_database命令来创建用于存放这一组示例的目 ...
- 揭秘日活千万腾讯会议全量云原生化上TKE技术实践
腾讯会议,一款联合国都Pick的线上会议解决方案,提供完美会议品质和灵活协作空间,广泛应用在政府.医疗.教育.企业等各个行业.大家从文章8天扩容100万核,腾讯会议是如何做到的?都知道腾讯会议背后的计 ...
- Unity WebGL WebSocket
在线示例 http://39.105.150.229/UnityWebSocket/ 快速开始 安装环境 Unity 2018.3 或更高. 无其他SDK依赖. 安装方法 通过 OpenUPM 安装 ...
- 【代码优化】Unity查漏补缺
1.XML: 使用Unity社区中的开源脚本(Js语言)解析XML文件,网址:http://dev.grumpyferret.com/unity/,已打包XMLParser.unitypackage, ...
- Unity代码混淆
https://www.zhihu.com/question/25414422 http://blog.csdn.net/kun1234567/article/details/7917847 http ...
- MySql数据库规范与原则
1.数据库表名命名规范 采用26个英文字母(区分大小写)和0-9的自然数(经常不需要)加上下划线'_'组成; 命名简洁明确,多个单词用下划线'_'分隔; 例如:user_login, user_pro ...
- Agumaster 改善了pagination
页面越来越完善了.
- URL 去重的 6 种方案!(附详细实现代码)
URL 去重在我们日常工作中和面试中很常遇到,比如这些: 可以看出,包括阿里,网易云.优酷.作业帮等知名互联网公司都出现过类似的面试题,而且和 URL 去重比较类似的,如 IP 黑/白名单判断等也经常 ...
- Linux:正则表达式2
基础正则表达式 ^ : 以xx开头 $ :以xx结尾 ^$ :代表空行 . 任意一个字符 \ :转义符号,让有着特殊意义的字符可被使用 * :重复0个或多个前面的字符,例如 a* 2表示a后面 ...
- C#开发PACS医学影像处理系统(十三):绘图处理之病灶测量
接上一篇文章,当我们可以绘制图形标记后,就可以在此操作类上面进行扩展, 比如测量类工具,目前整理出的常用绘图和测量功能如下: 测量工具类:(图形标记类请参考本系列文章:绘图处理之图形标记) 功能 说明 ...