动态线条
动态线条end

国家集训队]礼物

[国家集训队]礼物 

前置知识:

扩展卢卡斯定理

正文:

emmm,我可以说这是一道模板题吗。。。。

首先我们来分析题意:

简单来说,就是一共有 n 个不同的礼物和 m 个人,第 i 个人需要 wi 个礼物,问一共有多少种不同的方案数。

我们可以很轻松地看出这是一道与组合数有关的题目,对于总方案数,我们可以将其分解为 m 个不同的方案数的乘积(乘法原理),我们以题中的样例一为例:现在一共有 4 个礼物和 2 个人,第一个人要 1 个礼物,则第一个人取走礼物的方案数为 C41 种;第二个人要 2 个礼物,而在第一个人取走礼物后,还剩下 3 个礼物,所以第二个人取走礼物的方案数为 C32 种。所以,总方案数就为 C41C32=12 种。

现在我们改变取走礼物的顺序:让第二个人先取走两个礼物,这时第二个人取走礼物的方案数为 C42种,再让第一个人取走礼物的方案数,此时还剩下 2 个礼物,也就是说,第一个人取走礼物的方案数有 C21 种。所以,总方案数就为 C42C21=12 种。

我们可以发现,无论谁先取走礼物,最终结果都是一样的,所以我们只需按所给的礼物数量顺序计算就行。也就是说,总方案数的式子为:

(1)ans=Cnw1+Cnw1w2++Cnw1w2wmwm

然鹅,题目中还有无解的情况,那什么情况下会无解呢?很显然,只有在礼物不够用时才会无解,所以我们可以在计算答案的同时判断剩余的礼物是否为负数,若是,则输出 Impossible ,否则继续计算。

到这里为止,我们已经知道如何计算最终答案了,只要在途中取模就能得到答案,是不是很简单?

不,你想的太简单了

我们来看一下这一题的

emm,这模数有点大,感觉总有些不对的地方,再仔细看看。

???,什么情况?P 不一定为质数???

既然P不一定为质数,那我们就没法求组合数了啊,怎么办呢?这时候,便要用到扩展卢卡斯定理了。

什么?你问我什么是扩展卢卡斯定理?你康康这里别问我为什么这叫扩展卢卡斯,我也不知道他到底和卢卡斯定理有啥关系

总而言之,只要用了扩展卢卡斯定理,我们就能解决模数 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第一道黑题祭怎么感觉这道题有点水呢)

都看到这里了,点个赞吧各位大佬萌。

posted @   冘木  阅读(244)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· 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 让容器管理更轻松!
Live2D
欢迎阅读『国家集训队]礼物』
动态线条
动态线条end
点击右上角即可分享
微信分享提示