bzoj2142 礼物 组合数学&中国剩余定理

       显然多出来的物品分给另一个人,答案不变。那么答案为C(n,w1)*C(n-w1,w2)*...*C(n-w1-w2-...-wm-1=wm,wm)=n!/w1!/w2!/.../wm!。由于题目中P不是质数,所以要先分解质因数再用中国剩余定理还原答案。所以关键是求x!对pi^ai取模的值。而x!1*2*...*x≡(1*2*...*pi^ai)^(x/(pi^ai))*1*2*..*(x%pi^ai)(mod pi^ai)。然后把所有pi的倍数都取出来,剩下的值前半部分快速幂,后半部分递归处理即可。同时维护pi的次数即可。

       最后把所有的阶乘合并。分子不变,分母把pi的次数减去,然后求逆相乘即可。

AC代码如下(注意n,m与题目相反):

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define N 105

int cnt,n; ll P,m,a[N];
struct node{ ll p,sum,num,val; }c[N];
void fzt(ll x){
	ll i;
	for (i=2; i*i<=x; i++) if (!(x%i))
		for (c[++cnt].p=i,c[cnt].sum=1; !(x%i); x/=i){
			c[cnt].num++; c[cnt].sum*=i;
		}
	if (x!=1){ c[++cnt].p=x; c[cnt].num=1;  c[cnt].sum=x; }
}
ll ksm(ll x,ll y,ll mod){
	ll sum=1,base=x;
	while (y){
		if (y&1) sum=sum*base%mod;
		base=base*base%mod; y>>=1;
	}
	return sum;
}
void exgcd(ll u,ll v,ll &x,ll &y){
	if (!v){ x=1; y=0; return; } else{
		exgcd(v,u%v,y,x); y-=x*(u/v);
	}
}
ll getivs(ll x,ll p){
	ll t1,t2; exgcd(x,p,t1,t2);
	return (t1%p+p)%p;
}
ll crt(){
	int i; ll sum=0;
	for (i=1; i<=cnt; i++){
		ll t1,t2; exgcd(c[i].sum,P/c[i].sum,t1,t2);
		t2=(t2%P+P)%P; sum=(sum+P/c[i].sum*t2*c[i].val)%P;
	}
	return (sum%P+P)%P;
}
void solve(int k,ll x,ll &t1,ll &t2){
	ll i,u,v; t1=1; t2=x/c[k].p;
	for (i=1; i<c[k].sum; i++)
		if (i%c[k].p) t1=t1*i%c[k].sum;
	t1=ksm(t1,x/c[k].sum,c[k].sum);
	for (i=x-x%c[k].sum+1; i<=x; i++)
		if (i%c[k].p) t1=t1*i%c[k].sum;
	if (t2){
		solve(k,t2,u,v);
		t1=t1*u%c[k].sum; t2+=v;
	}
}
int main(){
	scanf("%lld%lld%d",&P,&m,&n); int i,j,sum=0;
	for (i=1; i<=n; i++){ scanf("%lld",&a[i]); sum+=a[i]; }
	if (sum>m){ puts("Impossible"); return 0; }
	if (sum<m) a[++n]=m-sum;
	fzt(P);
	for (i=1; i<=cnt; i++){
		ll t1,t2; solve(i,m,t1,t2);
		for (j=1; j<=n; j++){
			ll u,v; solve(i,a[j],u,v); t2-=v;
			t1=t1*getivs(u,c[i].sum)%c[i].sum;
		}
		c[i].val=t1*ksm(c[i].p,t2,c[i].sum)%c[i].sum;
	}
	printf("%lld\n",crt());
	return 0;
}

by lych

2015.12.13

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值