【luogu P3807】【模板】卢卡斯定理/Lucas 定理(含 Lucas 定理证明)

【模板】卢卡斯定理/Lucas 定理

题目链接:luogu P3807

题目大意

求 C(n,n+m)%p 的值。
p 保证是质数。

思路

Lucas 定理内容

对于非负整数 n,m,质数 p,有:
Cmni=0kCmini(mod p)
其中 m=mkpk+...+m1p+m0n=nkpk+...+n1p+n0。(其实就是 n,mp 进制展开)

那我们一般做题用的是递推式,也就是 CmnCm/pn/pCmmodpnmodp(mod p)
(当 n>m 时,我们规定 Cmn=0

啥时候会用

我们有时候要算组合数,可能 Cmnn,m 很大,这时候一般做题就会让他取模一个数 mod p

那如果 p>m,我们可以愉快的用这个式子求:Cmn=m!n!(mn)!
算出 n!(mn)! 的逆元,就可以搞。

可当 mp 的时候,分母的乘法逆元可能不存在。(因为 xp 的倍数的话 x 就没有模 p 的逆元)

那这个时候我们就可以用 Lucas 定理把这个组合数拆成几个 m<p 的,就可以搞了。

证明

证明 Lucas 定理之前,我们先证明两个式子。


式一:

CpipiCp1i10(mod p),(1i<p)
证明:
Cpi=p!i!(pi)!=pi(p1)!(i1)!(p1(i1))!=piCp1i1
由于 1i<p,故 i 会有 p 的逆元 invi
piCp1i1=p×invi×Cp1i1
那这个地方都是 p 的倍数,那它被 p 取模一定是 0,故得证。


式二:

根据二项式定理:
(1+n)pCp0+Cp1x+...+Cpp1xp1+Cppxp(mod p)
再根据式一 Cpi0(mod p),(1i<p),可以得到
(1+n)pCp0+Cppxp1+xp(mod p)


接着我们开始证明,先设 m/p=qm,n/p=qn,mmodp=rm,nmodp=rn
那有 m=qmp+rm,n=qnp+rn

接着我们继续用二项式定理:
(1+x)m=i=1mCmixi
然后我们把左边给化简:
(1+x)m=(1+x)qmp+rm=(1+x)qmp(1+x)rm=[(1+x)p]qm(1+x)rm(1+xp)qm(1+x)rm[]i=1qmCqmixipi=1rmCrmixi(mod p)

那就有:
i=1mCmixii=1qmCqmixipi=1rmCrmixi(mod p)

那对于任意一个数 z,必然会有一组 i,j 满足 xz=xpixj
不难看出这其实就是满足 z=pi+j,所以当且仅当 i=zpj=zmodp
那也就是说左边的 i 取任意一个,右边都有一个新的跟它对于恒等。
左边 i=x,右边的就分别是 i=xp,i=xmodp

那当 i=x,就有:
Cmnxn=CqmqnxqnpCrmrnxrnCmnxn=CqmqnCrmrnxqnp+rnCmnxn=CqmqnCrmrnxn
两边同乘 inv(xn),就有了 Cmn=CqmqnCrmrn

得证。

本题

其实逆元的话直接要用的话直接 xp2modp 更好,不用像我这样线性求出每个。

代码

#include<cstdio> #define ll long long using namespace std; ll T, n, m, p; ll jc[100001], inv[100001]; ll ksm(ll x, ll y) { ll re = 1; while (y) { if (y & 1) re = (re * x) % p; x = (x * x) % p; y >>= 1; } return re; } ll C(ll x, ll y) {//暴力算组合数 if (x > y) return 0; return ((jc[y] * inv[x]) % p * inv[y - x]) % p; } ll work(ll n, ll m) {//Lucas 定理 if (!n) return 1; return (work(n / p, m / p) * C(n % p, m % p)) % p; } int main() { scanf("%lld", &T); while (T--) { scanf("%lld %lld %lld", &n, &m, &p); jc[0] = 1; for (ll i = 1; i <= p; i++)//预处理阶乘与其逆元 jc[i] = (jc[i - 1] * i) % p; inv[p - 1] = ksm(jc[p - 1], p - 2); for (ll i = p - 2; i >= 0; i--) inv[i] = (inv[i + 1] * (i + 1)) % p; printf("%lld\n", work(n, n + m)); } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P3807.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(83)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示