组合数取模 合集
0. 介绍
组合数取模,即给定 ,求
下面按数据范围讨论一些解法:
/ | |||
---|---|---|---|
且 为素数 | 杨辉三角暴力递推 | Lucas 定理 | Lucas 定理 |
且 为素数 | 杨辉三角暴力递推 | 预处理阶乘及其逆元 | exLucas |
且 为互不相同的素数 | 杨辉三角暴力递推 | Lucas 定理 CRT 合并 | Lucas 定理 CRT 合并 |
无特殊限制 | 杨辉三角暴力递推 | exLucas | exLucas |
( 的第二档本来想表达 远大于 的,但这样 的档就不可做了 qwq)
( 的第三档就是 为 square-free number)
(CRT 指中国剩余定理)
时空复杂度:
做法 | 预处理时间复杂度 | 询问时间复杂度 | 空间复杂度 |
---|---|---|---|
杨辉三角暴力递推 | |||
预处理阶乘及其逆元 | |||
Lucas 定理 | 预处理 的组合数,取决于所用的算法 | 预处理 的组合数,取决于所用的算法 | |
Lucas 定理 CRT 合并 | 预处理 的组合数,取决于所用的算法 | 预处理 的组合数,取决于所用的算法 | |
exLucas | 无预处理, | ||
多组询问 exLucas(模数相同) |
在 Lucas 定理 CRT 合并中, 是模数, 是最大质因子
1. 杨辉三角暴力递推
我们有
(考虑 里选 ,讨论选不选第一个即可证明)
递推即可 .
2. 预处理阶乘及其逆元
众所周知
预处理阶乘及其逆元即可
3. Lucas 定理
Lucas 定理:
形式一
设 在 ( 是质数)进制下表示为
其中 , .
则有:
形式二(简单)
Lucas 定理的证明
设 ,且 ,则:
由于 且 是素数,所以 存在模 意义下的逆元,因此:
因为 ,故 .
由二项式定理,
除了 的项数,别的模 均为 ,所以:
现在我们设:
从而:
再由二项式定理有
而同时又有:
注意到 正好能取到 到 内所有整数一次,所以枚举 ,得
结合二项式定理,得
对比系数,得:
命题获证 .
用形式一可以写出一个递推的代码:
int Lucas(int n,int m,int p)
{
for (int i=0;i<p;i++)
{
C[i][0]=1;
for (int j=1;j<=i;j++)
{
C[i][j]=C[i-1][j-1]+C[i-1][j];
if (C[i][j]>=p) C[i][j]-=p;
}
} // init
int ans=1;
while (n||m){ans=ans*C[n%p][m%p]; n/=p; m/=p;}
return ans;
}
而用形式二则可以写出一个递归的代码:
ll lucas(ll n,ll m){return (n<m)?0:(n<p)?C(n,m):C(n%p,m%p)*lucas(n/p,m/p)%p;}
4. Lucas 定理 CRT 合并
令
则
用 Lucas 定理求组合数,然后 CRT 合并即可
正确性显然
5. exLucas
先把 分解质因数,令
用同样的做法,列出同余方程组,求出 然后 CRT 合并 .
那么问题来了, 怎么求?
我们知道,,由于逆元可能不存在,考虑提取出质因子 来
其中 为 中质因子 的次数, 同理 .
此时分母就存在逆元了,现在问题又转化成了求形如
先考虑如何计算 .
举个例子()
可以看出上式分为三个部分:
第一部分是 的幂,次数是小于等于 的 的倍数的个数(即 ).
第二部分是一个阶乘 ,即 ,可以递归求
第三部分是 中与 互质的部分的乘积,这一部分具有如下性质(加个 ):
一般的,有
整块快速幂小块暴力即可求出
回到原问题,求
分母全部由上述第一部分和第二部分贡献(第三部分和 互质)。而递归计算第二部分的时候已经除去了第二部分中的因子 ,所以最终的答案就是上述第二部分递归返回的结果和第三部分的乘积(与第一部分无关) .
然后一层层回去即可求出组合数取模 .
typedef long long ll;
ll qpow(ll a,ll n,ll p)
{
ll ans=1;
while (n)
{
if (n&1) ans=ans*a%p;
a=a*a%p; n>>=1;
} return ans;
}
namespace extra_lucas_detail
{
ll fac(ll n,ll p,ll pk)
{
if (!n) return 1;
ll ans=1;
for (int i=1;i<pk;i++)
if (i%p) ans=ans*i%pk;
ans=qpow(ans,n/pk,pk);
for (int i=1; i<=n%pk;i++)
if (i%p) ans=ans*i%pk;
return ans*fac(n/p,p,pk)%pk;
}
ll exgcd(ll a,ll b,ll& x,ll& y)
{
if (!b){x=1; y=0; return a;}
ll d=exgcd(b,a%b,x,y);
int z=x; x=y; y=z-y*(a/b);
return d;
}
ll inv(ll a,ll P){ll x,y,d=exgcd(a,P,x,y); return (d==1)?x%P:-1;}
ll C(ll n,ll m,ll p,ll pk)
{
if (n<m) return 0;
ll A=fac(n,p,pk),B=fac(m,p,pk),C=fac(n-m,p,pk),cnt=0;
for (ll i=n;i;i/=p) cnt+=i/p;
for (ll i=m;i;i/=p) cnt-=i/p;
for (ll i=n-m;i;i/=p) cnt-=i/p;
return A*inv(B,pk)%pk*inv(C,pk)%pk*qpow(p,cnt,pk)%pk;
}
}
ll exlucas(ll n,ll m,ll p)
{
using namespace extra_lucas_detail;
ll ans=0,P=p;
for (int i=2;(p>1)&&(i*i<=p);i++)
{
ll pk=1;
while (!(p%i)){p/=i; pk*=i;}
if (pk>1)
{
ll now=C(n,m,i,pk);
ans=(ans+now*(P/pk)%P*inv(P/pk,pk)%P)%P;
}
}
if (p>1)
{
ll now=C(n,m,p,p);
ans=(ans+now*(P/p)%P*inv(P/p,p)%P)%P;
} return (ans%P+P)%P;
}
以下是博客签名,正文无关
本文来自博客园,作者:yspm,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/14959770.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】