显然多出来的物品分给另一个人,答案不变。那么答案为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