惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
Webroot Blog
Webroot Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
V2EX - 技术
V2EX - 技术
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
Recorded Future
Recorded Future
S
Schneier on Security
I
InfoQ
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The GitHub Blog
The GitHub Blog
S
Security @ Cisco Blogs
O
OpenAI News
W
WeLiveSecurity
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
Simon Willison's Weblog
Simon Willison's Weblog
人人都是产品经理
人人都是产品经理
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
GbyAI
GbyAI
NISL@THU
NISL@THU
T
Tailwind CSS Blog
V
Visual Studio Blog
PCI Perspectives
PCI Perspectives
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Jina AI
Jina AI
D
DataBreaches.Net
B
Blog RSS Feed
N
News and Events Feed by Topic
N
News and Events Feed by Topic
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
腾讯CDC
Latest news
Latest news
V
Vulnerabilities – Threatpost
Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
V
V2EX
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Register - Security
The Register - Security
Help Net Security
Help Net Security

博客园 - RonChen

抽屉原理 区间合并 距离和的最小值 归并排序与逆序对 快速排序与快速选择 同余分析 差分约束 Treap 点分治 莫队算法 分块 扫描线 错排问题 Sprague-Grundy (SG) 函数及其应用 容斥原理 线性基 高斯消元 勒让德公式 次短路 分层图最短路 01 图最短路 洪水填充 双向搜索 迭代加深搜索 剪枝 最小表示法 表达式计算 KMP 算法 队列 高精度运算
卢卡斯定理
RonChen · 2026-03-27 · via 博客园 - RonChen

引理 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;
}

例题:P2480 [SDOI2010] 古代猪文

给定两个整数 \(n, d \ (\le 10^9)\),要求计算 \(g^{\sum\limits_{d|n} C_n^d} \bmod 999911659\)

题目要求的模数 \(M = 999911659\) 是一个质数,根据费马小定理,当 \(g \not\equiv 0 \pmod M\) 时,\(g^p \equiv g^{p \bmod (M-1)} \pmod M\),其中 \(p = \sum\limits_{d|n} C_n^d\)

因此,任务转化为求 \(p \bmod 999911658\)。注意,如果 \(g\)\(M\) 的倍数,则结果为 \(0\)

由于 \(999911658\) 不是质数,无法直接使用 Lucas 定理。观察发现 \(999911658 = 2 \times 3 \times 4679 \times 35617\),这四个数均为质数,可以分别计算 \(p\) 对这四个质数取模的结果,最后使用中国剩余定理(CRT)合并得到 \(p \bmod 999911658\)

参考代码
#include <cstdio>
const int MOD = 999911659;
const int P[] = {2, 3, 4679, 35617};
int f[40000], a[4];
// 快速幂
int qpow(int a, int b, int m) {
    int res = 1; 
    a %= m;
    while (b > 0) {
        if (b & 1) res = 1ll * res * a % m;
        a = 1ll * a * a % m;
        b >>= 1;
    }
    return res;
}
// 组合数 C(n, m) % p,利用预处理阶乘
int comb(int n, int m, int p) {
    if (m > n) return 0;
    if (m == 0 || m == n) return 1;
    return f[n] * qpow(f[m], p - 2, p) % p * qpow(f[n - m], p - 2, p) % p;
}
// Lucas 定理
int lucas(int n, int m, int p) {
    if (m == 0) return 1;
    return lucas(n / p, m / p, p) * comb(n % p, m % p, p) % p;
}
int main()
{
    int n, g; scanf("%d%d", &n, &g);
    // 特判 g 是 MOD 的倍数
    if (g % MOD == 0) {
        printf("0\n");
        return 0;
    }
    for (int i = 0; i < 4; i++) {
        int p = P[i];  
        f[0] = 1;
        for (int j = 1; j < p; j++) f[j] = f[j - 1] * j % p;
        a[i] = 0;
        for (int d = 1; d <= n / d; d++) {
            if (n % d == 0) {
                a[i] = (a[i] + lucas(n, d, p)) % p;
                if (d * d != n) {
                    a[i] = (a[i] + lucas(n, n / d, p)) % p;
                }
            }
        }
    }
    // CRT 合并
    int ans = 0;
    for (int i = 0; i < 4; i++) {
        int m = (MOD - 1) / P[i];
        int inv = qpow(m, P[i] - 2, P[i]);
        ans = (ans + 1ll * a[i] * m % (MOD - 1) * inv % (MOD - 1)) % (MOD - 1);
    }
    ans = qpow(g, ans, MOD);
    printf("%d\n", ans);
    return 0;
}