【国家集训队 2011】礼物

Posted by WHZ0325 on 2019-04-29, Viewed times

题目描述

有 $n$ 件礼物,打算送给 $m$ 个人,其中送给第 $i$ 个人的礼物数量为 $w_i$,求送出礼物的方案数,输出答案模 $p$ 的结果,无解输出 $Impossible$。

设 $p=p_1^{c_1}p_2^{c_2}p_3^{c_3}\dots p_t^{c_t}$,$p_i$ 为质数。$1\le n\le 10^9$,$1\le m\le 5$,$1\le p_i^{c_i}\le 10^5$。

算法分析

以为是水题,看来我还是 naive……

首先不难发现答案就是 $C_n^{w_1}C_{n-w_1}^{w_2}\dots C_{n-w_1-w_2-\dots w_{m-1}}^{w_m}$,考虑如何计算这个式子。

$m$ 比较小,直接枚举计算每个组合数的值乘起来就是最终的答案,由于 $n$ 较大,$p$ 不一定为质数,每个组合数的值看起来不那么好算。

考虑 ExLucas 定理?由于 Lucas 定理的前提是模数为质数,而每个质因子上的指数可能大于 $1$,因此无法分解模数后 Lucas 定理再使用中国剩余定理合并。

这样 Lucas 定理的部分就只能直接算了,还是考虑分解模数然后用中国剩余定理合并,要保证数与模数互质才能用扩展欧几里得算法求出该数的逆元套用组合数公式,所以把阶乘中的 $p_i$ 提取出来,同时计算 $n!\mod p_i^{k_i}$ 剩下的部分。

记 $s_i=p_i^{k_i}$,可以将 $n!$ 分解一下,有 $\lfloor\frac{n}{p_i}\rfloor$ 个数是 $p_i$ 的倍数,这部分将他们都除以 $p_i$ 后就变成了 $\lfloor\frac{n}{p_i}\rfloor!$,是原问题的子问题,递归下去就可以了。剩下的不为 $p_i$ 倍数的部分应该怎么算呢?可以发现一个性质,那就是每 $s_i$ 个数(包含 $p_i$ 的倍数)中剩下的数乘积为定值,求出多少这样的块,乘方一下然后对后面不满的块单独计算就好了。

时间复杂度大概是 $O(5\times 20^2\times 10^5)$,不太好分析,总之肯定能过的。

参考题解:CSDN

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <cstdio>
#define N 100005
typedef long long int ll;
int p[25],c[25],s[25],cnt=0;
inline int inc(int x,int y,int p) {x+=y;return x>=p?x-p:x;}
inline int qpow(int n,int k,int p) {
int ans=1;
while(k) {
if(k&1) ans=(ll)ans*n%p;
n=(ll)n*n%p;k>>=1;
}
return ans;
}
void exgcd(int a,int b,int &x,int &y) {
if(!b) {x=1;y=0;}
else {exgcd(b,a%b,y,x);y-=a/b*x;}
}
inline int rev(int a,int p) {int x,y;exgcd(a,p,x,y);return (x%p+p)%p;}
inline int calc(int n,int i) {
if(!n) return 1;
int ans=1;
for(register int j=2;j<=s[i];++j) if(j%p[i]) ans=(ll)ans*j%s[i];
ans=qpow(ans,n/s[i],s[i]);
for(register int j=n/s[i]*s[i]+1;j<=n;++j) if(j%p[i]) ans=(ll)ans*j%s[i];
return (ll)ans*calc(n/p[i],i)%s[i];
}
int C(int n,int m,int i) {
if(n<m) return 0;int k=0,a=calc(n,i),b=calc(m,i),c=calc(n-m,i);
for(register int j=n;j;j/=p[i]) k+=j/p[i];
for(register int j=m;j;j/=p[i]) k-=j/p[i];
for(register int j=n-m;j;j/=p[i]) k-=j/p[i];
return (ll)a*rev(b,s[i])%s[i]*rev(c,s[i])%s[i]*qpow(p[i],k,s[i])%s[i];
}
int mod,w[10];
inline int CRT(int n,int m) {
int res=0;
for(register int i=1;i<=cnt;++i) {
res=inc(res,(ll)rev(mod/s[i],s[i])*(mod/s[i])%mod*C(n,m,i)%mod,mod);
}
return res;
}
int main() {
int n,m,sum=0;scanf("%d%d%d",&mod,&n,&m);
for(register int i=1;i<=m;++i) {scanf("%d",w+i);sum+=w[i];}
if(sum<=n) {
int t=mod;
for(register int i=2;i<=t;++i) if(t%i==0) {
p[++cnt]=i;s[cnt]=1;while(t%i==0) {++c[cnt];t/=i;s[cnt]*=i;}
}
int ans=1;
for(register int i=1;i<=m;++i) {ans=(ll)ans*CRT(n,w[i])%mod;n-=w[i];}
printf("%d\n",ans);
}
else puts("Impossible");
return 0;
}