Bzoj2142礼物:组合数取模

10 篇文章 0 订阅
7 篇文章 0 订阅

题目链接:2142:礼物

组合数学推公式是很简单的,关键是取模

注意p并不是质数,而且p可能很大,所以lucas在这道题上并没有什么卵用

存在这样一个事实:设P分解质因数后有一项为pi^ai,那么C(x,y)%p%(pi^ai)=x%(pi^ai),设为xi

而题目中已经给出pi^ai<=100000,所以我们只要计算出所有的xi,然后CRT一下就可以算出C(x,y)了

现在问题转化为怎么求xi

考虑把n!拆开得到1*2*3*4*5*6*...*n,显然和pi^ai不互质,在%(pi^ai)的意义下并没有逆元

所以我们把这n项全%(pi^ai),得到1*2*3*...*pi^ai-1*1*2*3*...*pi^ai-1...*(pi^ai)^?*(1+2+3+....)

前面一段是循环的,中间有一段是循环节的一部分,后面(pi^ai)^(balabala)在组合数公式中是可以上下相减来消去的

所以前面一段快速幂,递归处理即可

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=500010;
LL n,m,mod,cnt=0,num=0,sum=0;
struct Yinz{LL v,s;}yz[maxn];
LL w[maxn],jc[maxn];

void get_devide(){
	LL tmp=mod;
	for (int i=2;i<=sqrt(tmp);++i)
	    if (tmp%i==0){
			yz[++cnt].v=i; yz[cnt].s=1;
			while(tmp%i==0)yz[cnt].s*=i,tmp/=i;
	    }
	if (tmp>1) yz[++cnt].v=tmp,yz[cnt].s=tmp;
}

LL powe(LL x,LL y,LL mod){
    LL ret=1;
    while (y){
		if (y&1) ret=ret*x%mod;
		x=x*x%mod; y>>=1;
    }return ret;
}

LL get_val(LL N,LL x,LL y){
	if (N<x) return jc[N]; num+=N/x;
	return jc[N%y]*powe(jc[y-1],N/y,y)%y*get_val(N/x,x,y)%y;
}

void exgcd(LL a,LL b,LL &x,LL &y){
	int tmp;
	if(!b){x=1;y=0;return;}
	exgcd(b,a%b,x,y);
	tmp=x; x=y; y=tmp-(a/b)*y;
}

LL get_inv(LL a,LL b){
	LL x,y; exgcd(a,b,x,y);
	return (x%b+b)%b;
}

LL solve(LL x,LL y){
	jc[0]=1; for(int i=1;i<y;++i) jc[i]=jc[i-1]*(i%x?i:1)%y;
	num=0; LL tmp1=get_val(n,x,y),tmp2=1; LL tmp=num; num=0;
	for (int i=1;i<y;++i) jc[i]=jc[i-1]*(i%x?get_inv(i,y):1)%y;
	for (int i=1;i<=m;++i) tmp2=tmp2*get_val(w[i],x,y)%y;
	return tmp1*tmp2%y*powe(x,tmp-num,y)%y;
}

int main(){
	scanf("%lld%lld%lld",&mod,&n,&m);
	LL sum=0;
	for (int i=1;i<=m;++i){
		scanf("%lld",&w[i]);
		sum+=w[i];
		if (sum>n) {printf("Impossible\n");return 0;}
	}
	if (sum<n) w[++m]=n-sum;
	get_devide();//mod=pi^ai;
	LL ans=0;
	for (int i=1;i<=cnt;++i){
		int now=mod/yz[i].s;
		LL tmp1=solve(yz[i].v,yz[i].s);
		LL tmp2=get_inv(now,yz[i].s);
		ans=(ans+tmp1*tmp2%mod*now%mod)%mod;
	}
	printf("%lld",ans);
}




  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值