扩展卢卡斯定理学习笔记
IV.exLucas(扩展卢卡斯定理)
虽然是这个名字,但是它跟常规卢卡斯没有半毛钱关系
exLucas也是用来计算 的。不同于普通Lucas,这里的 可以不为质数。
对于不为质数的模数,一个常规的想法是对其分解质因数,然后考虑其对于每个质数的次幂 的模数,最后用CRT将所有东西怼一块即可。
于是我们现在考虑 。首先,一个显然的想法是,发现仅有 的倍数在模 意义下无逆元,于是我们求出 这三个数中有 的多少次幂, 的幂次部分可以直接相减得到最终组合数中有多少个 的次幂,而剩余部分可以直接求逆元处理。
于是问题变成两个:如何求一个阶乘中有多少个 ,以及除去 后剩余了什么。
首先,第一个问题是常规问题,令 表示 中有多少个 ,则 中所有 的倍数显然是 的唯一来源,对这些 的倍数各除上一个 后显然又会出现新的一组阶乘,故如此递归下去即可。边界为 。递推式为 。
然后,同理,第二个问题也可以分成两部分: 的倍数以及非 的倍数。 的倍数就除去 继续递归即可,非 的倍数,显然其具有 的循环节,于是预处理出 中所有数的阶乘(阶乘中要除去 的倍数),则单个循环节内所有东西的积明显是 (当然,除去 的倍数),循环节个数是 ,于是做一个快速幂即可。至于剩余的未完结的循环节,就直接查表就行了,反正我们已经预处理出来了。
时间复杂度 ,因为对于每一个质因数 都要 地预处理阶乘,这部分复杂度共 ;质因数数量是 级别的,然后对于每个质因数都要递归地处理 ,明显递归层数为 。
IV.I.【模板】扩展卢卡斯
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m;
int P,M,tot,p[30],a[30],b[30],k[30],fac[1001000],phi[30],res;
ll calcfacb(ll ip,int id){
if(!ip)return 0;
return calcfacb(ip/b[id],id)+ip/b[id];
}
int ksm(int x,ll y,int mod){
int z=1;
for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;
return z;
}
int calcfacm(ll ip,int id){
if(!ip)return 1;
return 1ll*ksm(fac[p[id]],ip/p[id],p[id])*fac[ip%p[id]]%p[id]*calcfacm(ip/b[id],id)%p[id];
}
int C(ll x,ll y,int id){
int A=calcfacm(x,id);
A=1ll*A*ksm(calcfacm(y,id),phi[id]-1,p[id])%p[id]*ksm(calcfacm(x-y,id),phi[id]-1,p[id])%p[id];
ll B=calcfacb(x,id)-calcfacb(y,id)-calcfacb(x-y,id);
return 1ll*A*ksm(b[id],B,p[id])%p[id];
}
void newprime(int i){
b[++tot]=i,p[tot]=1;while(!(P%i))k[tot]++,P/=i,p[tot]*=i;
phi[tot]=p[tot]/i*(i-1);
fac[0]=1;for(int j=1;j<=p[tot];j++)if(j%i)fac[j]=1ll*fac[j-1]*j%p[tot];else fac[j]=fac[j-1];
a[tot]=C(n,m,tot);
}
int main(){
scanf("%lld%lld%d",&n,&m,&P),M=P;
for(int i=2;i*i<=P;i++)if(!(P%i))newprime(i);
if(P!=1)newprime(P);
for(int i=1;i<=tot;i++)(res+=1ll*a[i]*ksm(M/p[i],phi[i]-1,p[i])%M*(M/p[i])%M)%=M;
printf("%d\n",res);
return 0;
}
IV.II.[国家集训队]礼物
其实也是模板,但是又写了一道练手。
感觉这次的代码比上一题要清爽很多?
代码:
#include<bits/stdc++.h>
using namespace std;
int p,n,m,a[10];
int ksm(int x,int y,int mod){
int z=1;
for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;
return z;
}
int B,K,P,phi,fac[100100];
int M,A;
int calcB(int x){int ret=0;while(x)ret+=(x=x/B);return ret;}
int calcfac(int x){if(!x)return 1;return 1ll*ksm(fac[P-1],x/P,P)*fac[x%P]%P*calcfac(x/B)%P;}
int C(int x,int y){return 1ll*calcfac(x)*ksm(calcfac(y),phi-1,P)%P*ksm(calcfac(x-y),phi-1,P)%P*ksm(B,calcB(x)-calcB(y)-calcB(x-y),P)%P;}
void func(int ip){
B=ip,P=1,K=0;while(!(p%B))p/=B,K++,P*=B;
phi=P/B*(B-1);
fac[0]=1;for(int i=1;i<P;i++)if(i%B)fac[i]=1ll*fac[i-1]*i%P;else fac[i]=fac[i-1];
int S=1;for(int i=1,j=n;i<=m;i++)S=1ll*S*C(j,a[i])%P,j-=a[i];
// printf("%d %d %d %d:%d\n",B,K,P,phi,S);
(A+=1ll*S*(M/P)%M*ksm((M/P),phi-1,P)%M)%=M;
}
int main(){
scanf("%d%d%d",&p,&n,&m),M=p;for(int i=1;i<=m;i++)scanf("%d",&a[i]);
for(int i=1,j=n;i<=m;i++){j-=a[i];if(j<0){puts("Impossible");return 0;}}
for(int i=2;i*i<=p;i++)if(!(p%i))func(i);
if(p!=1)func(p);
printf("%d\n",A);
return 0;
}
IV.III.[SDOI2013]方程
稍微不那么板子一点了。
首先,对于 一类的限制,明显可以提前将 配给 使得其限制同其它数一样,都是 。
然后,对于 一类限制,反正 很小,直接容斥就行了。
于是问题转换为 个正整数和为 的方案数。依照小学数学·隔板法,其方案数为 。用exLucas随便算即可。
时间复杂度 。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,p,n,n1,n2,a[10];
ll m;
int ksm(int x,int y,int mod){
int z=1;
for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;
return z;
}
int B,K,P,phi,fac[100100];
int M,A;
int calcB(int x){int ret=0;while(x)ret+=(x=x/B);return ret;}
int calcfac(int x){if(!x)return 1;return 1ll*ksm(fac[P-1],x/P,P)*fac[x%P]%P*calcfac(x/B)%P;}
int C(int x,int y){if(x<0||x<y)return 0;return 1ll*calcfac(x)*ksm(calcfac(y),phi-1,P)%P*ksm(calcfac(x-y),phi-1,P)%P*ksm(B,calcB(x)-calcB(y)-calcB(x-y),P)%P;}
void func(int ip){
B=ip,P=1,K=0;while(!(p%B))p/=B,K++,P*=B;
phi=P/B*(B-1);
fac[0]=1;for(int i=1;i<P;i++)if(i%B)fac[i]=1ll*fac[i-1]*i%P;else fac[i]=fac[i-1];
int S=0;for(int i=0;i<(1<<n1);i++){
ll newm=m;
for(int j=0;j<n1;j++)if(i&(1<<j))newm-=a[j];
if(newm<0)continue;
if(__builtin_popcount(i)&1)(S+=P-C(newm-1,n-1))%=P;else (S+=C(newm-1,n-1))%=P;
}
(A+=1ll*S*(M/P)%M*ksm((M/P),phi-1,P)%M)%=M;
}
int main(){
scanf("%d%d",&T,&M);
while(T--){
p=M,scanf("%d%d%d%lld",&n,&n1,&n2,&m),A=0;
for(int i=0;i<n1;i++)scanf("%d",&a[i]);
for(int j=0,x;j<n2;j++)scanf("%d",&x),m-=(x-1);
if(m<0){puts("0");continue;}
for(int i=2;i*i<=p;i++)if(!(p%i))func(i);
if(p!=1)func(p);
printf("%d\n",A);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?