国家集训队]礼物
[国家集训队]礼物
前置知识:
扩展卢卡斯定理
正文:
emmm,我可以说这是一道模板题吗。。。。
首先我们来分析题意:
简单来说,就是一共有 个不同的礼物和 个人,第 个人需要 个礼物,问一共有多少种不同的方案数。
我们可以很轻松地看出这是一道与组合数有关的题目,对于总方案数,我们可以将其分解为 个不同的方案数的乘积(乘法原理),我们以题中的样例一为例:现在一共有 个礼物和 个人,第一个人要 个礼物,则第一个人取走礼物的方案数为 种;第二个人要 个礼物,而在第一个人取走礼物后,还剩下 个礼物,所以第二个人取走礼物的方案数为 种。所以,总方案数就为 种。
现在我们改变取走礼物的顺序:让第二个人先取走两个礼物,这时第二个人取走礼物的方案数为 种,再让第一个人取走礼物的方案数,此时还剩下 个礼物,也就是说,第一个人取走礼物的方案数有 种。所以,总方案数就为 种。
我们可以发现,无论谁先取走礼物,最终结果都是一样的,所以我们只需按所给的礼物数量顺序计算就行。也就是说,总方案数的式子为:
然鹅,题目中还有无解的情况,那什么情况下会无解呢?很显然,只有在礼物不够用时才会无解,所以我们可以在计算答案的同时判断剩余的礼物是否为负数,若是,则输出 ,否则继续计算。
到这里为止,我们已经知道如何计算最终答案了,只要在途中取模就能得到答案,是不是很简单?
不,你想的太简单了
我们来看一下这一题的
emm,这模数有点大,感觉总有些不对的地方,再仔细看看。
???,什么情况? 不一定为质数???
既然P不一定为质数,那我们就没法求组合数了啊,怎么办呢?这时候,便要用到扩展卢卡斯定理了。
什么?你问我什么是扩展卢卡斯定理?你康康这里 。别问我为什么这叫扩展卢卡斯,我也不知道他到底和卢卡斯定理有啥关系
总而言之,只要用了扩展卢卡斯定理,我们就能解决模数 不是质数的情况,这也是这道题真正的考点。接下来,只要带入模板,将模数分解计算即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll pow_mod(ll x,ll n,ll mod){
ll res=1;
while(n>0){
if (n&1) res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
if (b==0){
x=1;
y=0;
return a;
}
ll d=exgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-a/b*y;
return d;
}
ll CRT(ll n,ll *a,ll *b){
ll M=1,res=0;
for (ll i=0;i<n;i++) M=M*b[i];
for (ll i=0;i<n;i++) {
ll x,y;
ll g=M/b[i];
exgcd(g,b[i],x,y);
res=(res+g*x*a[i])%M;
if (res<0) res+=M;
}
return res;
}
ll cal(ll n,ll p,ll mod){
if (n==0) return 1;
ll res=1;
for (ll i=1;i<=mod;i++){
if (i%p!=0){
res=res*i%mod;
}
}
res=pow_mod(res,n/mod,mod);
for (ll i=1;i<=n%mod;i++){
if (i%p){
res=res*i%mod;
}
}
return res*cal(n/p,p,mod)%mod;
}
ll work(ll n,ll m,ll p,ll mod){
ll 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 pow_mod(p,cnt,mod)*cal(n,p,mod)%mod*
pow_mod(cal(m,p,mod),mod/p*(p-1)-1,mod)%mod*
pow_mod(cal(n-m,p,mod),mod/p*(p-1)-1,mod)%mod;
}
ll com(ll n,ll m,ll p){
if (n<m) return 0;
ll a[20],b[20],tot=0;
for (int i=2;i*i<=p;i++){
if (p%i==0){
b[tot]=1;
while(p%i==0) p/=i,b[tot]*=i;
a[tot]=work(n,m,i,b[tot]);
tot++;
}
}
if (p>1){
b[tot]=p;
a[tot]=work(n,m,p,b[tot]);
tot++;
}
return CRT(tot,a,b);
}
int main(){
ll p;
scanf("%lld",&p);
ll n,m;
scanf("%lld %lld",&n,&m);
ll ans=1;
for (int i=0;i<m;i++){
ll w;
scanf("%lld",&w);
if (n<0 || n<w){
puts("Impossible");
return 0;
}
ans=ans*com(n,w,p)%p;
n-=w;
}
printf("%lld\n",ans);
return 0;
}
最后:ac第一道黑题祭(怎么感觉这道题有点水呢)
都看到这里了,点个赞吧各位大佬萌。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!