当前位置: 首页 > news >正文

卢卡斯定理

引理 1\(C_{p}^x \equiv 0 \pmod{p}, \ 0 \lt x \lt p\)。因为 \(C_p^x = \dfrac{p!}{x!(p-x)!} = \dfrac{p(p-1)!}{x(x-1)!(p-x)!} = \dfrac{p}{x} C_{p-1}^{x-1}\),所以 \(C_p^x \equiv p \cdot inv_{x} \cdot C_{p-1}^{x-1} \equiv 0 \pmod{p}\)

引理 2\((1+x)^p \equiv 1 + x^p \pmod{p}\)。二项式定理 \((1+x)^p = \sum \limits_{i=0}^p C_p^i x^i\),由引理 1 得只剩 \(i=0,p\) 两项。

大组合数取模问题:给定整数 \(n,m,p\) 的值,求出 $C_n^m \pmod{p} $ 的值。其中 \(1 \le m \le n \le 10^{18}, \ 1 \le p \le 10^5\),保证 \(p\) 为质数。

卢卡斯(Lucas)定理\(C_n^m \equiv C_{n/p}^{m/p} C_{n \bmod p}^{m \bmod p} \pmod{p}\),其中 \(p\) 为质数。\(n \bmod p\)\(m \bmod p\) 一定是小于 \(p\) 的数,可以直接求解,\(C_{n/p}^{m/p}\) 可以继续用 Lucas 定理求解。
边界条件:当 \(m=0\) 时,结果为 \(1\)

证明:令 \(n = ap + b, \ m = cp + d\)

\((1+x)^n \equiv \sum \limits_{i=0}^n C_n^i x^i \pmod{p}\),其中 \(x^m\) 的系数为 \(C_n^m\)

\((1+x)^n \equiv (1+x)^{ap+b} \equiv (1+x^p)^a \cdot (1+x)^b \equiv \sum \limits_{i=0}^a C_a^i x^{ip} \cdot \sum \limits_{j=0}^{b} C_b^j x^b \pmod{p}\),其中 \(x^m = x^{cp} \cdot x^d\) 的系数为 \(C_a^c C_b^d\)

所以 \(C_n^m \equiv C_a^c C_b^d \pmod{p}\),即 \(C_n^m \equiv C_{n/p}^{m/p} C_{n \bmod p}^{m \bmod p} \pmod{p}\)

void init(int p) {f[0] = 1; g[0] = 1;for (int i = 1; i < p; i++)f[i] = 1ll * f[i - 1] * i % p;g[p - 1] = qpow(f[n - 1], p - 2, p);for (int i = p - 2; i >= 1; i--) g[i] = 1ll * g[i + 1] * (i + 1) % p;
}
int C(int n, int m, int p) {if (n < m) return 0;return 1ll * f[n] * g[m] % p * g[n - m] % p;
}
int lucas(ll n, ll m, int p) {if (m == 0) return 1;return 1ll * lucas(n / p, m / p, p) * C(n % p, m % p, p) % p;
}

时间复杂度:预处理 \(O(p)\),计算一次组合数 \(O(\log_p n)\)

习题:P3807 【模板】卢卡斯定理/Lucas 定理

解题思路

因为 \(n,m\) 有可能大于等于 \(p\),且 \(p\) 为质数,所以使用 Lucas 定理。

需要注意每次 \(p\) 不同,所以需要重新推一下阶乘和阶乘逆元,并且是预处理到 \(p-1\)

参考代码
#include <cstdio>
const int N = 100005;
int f[N], g[N];
int qpow(int x, int y, int p) {int res = 1;while (y > 0) {if (y & 1) res = 1ll * res * x % p;x = 1ll * x * x % p;y >>= 1;}return res;
}
void init(int p) {f[0] = 1; g[0] = 1;for (int i = 1; i < p; i++) f[i] = 1ll * f[i - 1] * i % p;g[p - 1] = qpow(f[p - 1], p - 2, p);for (int i = p - 2; i > 0; i--)g[i] = 1ll * g[i + 1] * (i + 1) % p;
}
int C(int n, int m, int p) {if (n < m) return 0;return 1ll * f[n] * g[m] % p * g[n - m] % p;
}
int lucas(int n, int m, int p) {if (m == 0) return 1;return 1ll * lucas(n / p, m / p, p) * C(n % p, m % p, p) % p;
}
void solve() {int n, m, p; scanf("%d%d%d", &n, &m, &p);init(p);printf("%d\n", lucas(n + m, n, p));
}
int main()
{int t; scanf("%d", &t);for (int i = 1; i <= t; i++) solve();return 0;
}

习题:P2606 [ZJOI2010] 排列计数

解题思路

满足要求的排列相当于一棵二叉树,父亲节点的数小于儿子节点的数(小根堆)。

根肯定是最小数 \(1\),然后把剩下 \(n-1\) 个数分成左右子树两部分,设左子树大小为 \(left\),右子树大小为 \(right\),则有 \(C(n-1,left)\) 种分法,问题就被拆成了大小为 \(left\)\(right\) 的两个子问题。

因此存在一种 DP 方法,\(dp_i = dp_{left_i} \times dp_{i-1-left_i} \times C_{i-1}^{left_i}\),其中 \(left_i\) 表示当大小为 \(i\) 时,其根的左子树的大小。

因为 \(n\) 有可能大于等于模数,且模数为质数,所以组合数使用 Lucas 定理求。

考虑 \(left\) 怎么求?一个新节点跟它的父节点同处于左子树或右子树,则有起始条件:\(2\) 在左子树,\(3\) 在右子树,后面的节点可以用父节点情况递推。

所以如果 \(i\) 在左子树,\(left_i = left_{i-1} + 1\),否则 \(left_i = left_{i-1}\)

参考代码
#include <cstdio>
#include <algorithm>
using std::min;
const int N = 1000005;
bool flag[N]; // true表示在左子树,false表示在右子树
int ans[N], sz_left[N], f[N], g[N];
int qpow(int x, int y, int mod) {int res = 1;while (y > 0) {if (y & 1) res = 1ll * res * x % mod;x = 1ll * x * x % mod;y >>= 1;}return res;
}
void init(int n, int p) {f[0] = g[0] = 1;for (int i = 1; i < n; i++) f[i] = 1ll * f[i - 1] * i % p;g[n - 1] = qpow(f[n - 1], p - 2, p);for (int i = n - 2; i > 0; i--) g[i] = 1ll * g[i + 1] * (i + 1) % p;
}
int C(int n, int m, int p) {if (n < m) return 0;return 1ll * f[n] * g[m] % p * g[n - m] % p;
}
int lucas(int n, int m, int p) {if (m == 0) return 1;return 1ll * lucas(n / p, m / p, p) * C(n % p, m % p, p) % p;
}
int main()
{int n, m; scanf("%d%d", &n, &m);ans[0] = ans[1] = 1; flag[2] = 1; flag[3] = 0;ans[2] = 1; ans[3] = 2;sz_left[2] = 1; sz_left[3] = 1;init(min(n, m), m);for (int i = 4; i <= n; i++) {flag[i] = flag[i / 2];sz_left[i] = sz_left[i - 1] + flag[i];ans[i] = 1ll * lucas(i - 1, sz_left[i], m) * ans[sz_left[i]] % m * ans[i - 1 - sz_left[i]] % m; }printf("%d\n", ans[n]);return 0;
}
http://www.jsqmd.com/news/545816/

相关文章:

  • 2026如何选方案?数据越多,模型越复杂,为什么风光功率预测反而“更不准”了?
  • python基于微信小程序的方言文化传播平台的设计与开发
  • k8s中docker cri
  • 终极指南:如何为ente/auth开发自定义插件扩展功能
  • ai赋能设计:基于快马探索solidworks装配体的智能布局与优化思路
  • 老旧电脑焕新生:OpenClaw远程调用Qwen3-32B-Chat提升低配设备能力
  • Lobe Theme:重构Stable Diffusion WebUI体验的现代化主题
  • 从零到精通的嵌入式Linux与单片机学习路线对比
  • 如何快速实现Redux-Saga与Next.js集成:终极服务端渲染异步状态管理指南
  • python-flask-djangol框架的高校毕业生就业信息实习管理系统
  • python基于微信小程序的旅游攻略分享平台
  • 24周Web开发入门指南:微软官方完整课程助你从零开始
  • GME-Qwen2-VL-2B-Instruct部署案例:信创环境(麒麟/UOS)下本地运行实录
  • 分享一套锋哥原创的的AI大模型-基于LangChain的RAG健康知识智能问答系统(Flask+Vue3+Ollama+Chroma)
  • ente/auth日志系统解析:监控与调试技巧
  • 巨有科技:银发文旅风口来了!康养旅游这样做才赚
  • 电商用户评价分析实战:用Python+SnowNLP打造情感分析工具(附代码)
  • 虚拟化管理工具实战指南:如何通过virt-manager实现高效虚拟机管理
  • QT窗口特效实战:从透明到异形控件的全方位实现指南
  • # 发散创新:边缘容器中的轻量级服务部署实战与优化策略在云计算向边缘计算演进的浪潮中,**边缘容器技术**正成
  • Java高频面试题:ShardingSphere的核心模块有哪些?他们是如何工作的?
  • HP-Socket代码重构工作量估算准确性分析:偏差与改进
  • RPA-Python与pytest-buildah集成:Buildah测试自动化
  • 利玛窦的记忆宫殿 - liyan
  • Obsidian Local Images Plus 终极指南:如何一键解决所有本地图片管理难题
  • Zotero插件Ethereal Style:打造高效文献管理新体验
  • PVE 部署 iStoreOS 软路由完整教程(避坑版)
  • COMSOL仿真技术在变压器电磁场模型研究中的应用:探究磁密分布与电路状态结果
  • OpenClaw学习助手:GLM-4.7-Flash实现的错题本自动整理
  • 3步突破分子构象采样瓶颈:从理论到药物研发落地