一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模P后的结果。
一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模P后的结果。
输入的第一行包含一个正整数P,表示模;第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。
若不存在可行方案,则输出“Impossible”,否则输出一个整数,表示模P后的方案数。
题解:组合数学+数论
这道题就是求c(n,sum)*c(sum,w[1])*c(sum-w[1],w[2])...*c(w[m],w[m])%p
式子其实可以进一步化简变成
n!/(w[1]!*w[2]!*..*w[m]!)%p
如果这里的p,是质数的话,很容易想到用lucas定理来做,但是这里p不是质数,那怎么办呢?
首先考虑把p分解,P=p1^c1 * p2^c2 * p3^c3 * … *pt ^ ct,pi为质数。
我们考虑分开处理,得到t个ans%(pi^ci)=ai 的一次同余方程,然后用中国剩余定理合并。
中国剩余定理(CRT)的表述如下
设正整数两两互素,则同余方程组
有整数解。并且在模下的解是唯一的,解为
其中,而为模意义下的逆元。
知道中国剩余定理后,就要考虑怎么求c(n,m)%pi^ci ,c(n,m)=n!/(m!(n-m)!) 因为有除法所以我们需要用到乘法逆元,但是pi^ci不是质数不能用费马小定理,所以无法用快速幂来求,因为m!,(n-m)!与pi^ci不互质所以我们也不能直接用扩展欧几里德解ax=1 (mod b) gcd(a,b)=1 , ax+by=1 ,但是我们可以考虑提取公因数让其互质。
举例说明:
n!=t1*pi^c1
m!=t2*pi^c2
(n-m)!=t3*pi^c3
那么怎样快速求解t1,t2,t3,c1,c2,c3呢?
以19!为例,假设pi=3
19!=1*2*3...*19
=1*2*4*5*7*8*10*11*13*14*16*17*19*(3*1*3*2*3*3*3*4*3*5*3*6)
=1*2*4*5*7*8*10*11*13*14*16*17*19*3^6(1*2*3*4*5*6)
对于上面这个式子我们需要求出 [1, pi^ci - 1] 中除去 p 的倍数的其余数的前缀积(类似阶乘少了 p 的倍数)。
然后我们知道 [1, n] 中包含 pi的数有 n / pi 个,我们将这些数中都提取出 1 个 pi,那么就获得了 pi^(n/pi),然后这 n / pi 个数就变成了 [1, n/pi],就可以递归下去。
其余的部分可以分段来求,分成 [1, pi^ci - 1], [pi^ci + 1, pi^ci + pi^ci - 1] ..... 这样,每一段的积都是一样的 (mod pi^ci 意义下),(至于为什么一样我就不知道啦,自己手算验证一下好了)直接快速幂就可以了。
最后c(n,m)%pi^ci 的答案就是t1*inverse(t2)*inverse(t3)*pi^(c1-c2-c3) %pi^ci
#include
#include
#include
#include
#include
#define LL long long
#define N 200003
#define pa pair
using namespace std;
LL n,m,p,sum;
LL w[N],prime[N],cnt[N],mod[N],a[N];
int tot;
void get_prime()//模数分解成P=p1^c1 * p2^c2 * p3^c3 * … *pt ^ ct,pi为质数的形式
{
LL x=p;
for (LL i=2;i*i<=x;i++)
if (x%i==0)
{
prime[++tot]=i; cnt[tot]=0; mod[tot]=1;
while (x%i==0)
{
x/=i;
cnt[tot]++; mod[tot]*=i;
}
}
if (x>1)
{
prime[++tot]=x;
cnt[tot]=1;
mod[tot]=x;
}
}
LL quickpow(LL num,LL x,LL m)//快速幂
{
LL ans=1;
LL base=num%m;
while (x)
{
if (x&1) ans=(ans*base)%m;
x>>=1;
base=(base*base)%m;
}
return ans%m;
}
void exgcd(LL a,LL b,LL &x,LL &y)//扩展欧几里德算法
//ax=1 (mod b) gcd(a,b)==1 ax+by=1的解,x为a的逆元
{
if (b==0)
{
x=1; y=0;
return;
}
exgcd(b,a%b,x,y);
LL t=y;
y=x-(a/b)*y;
x=t;
}
LL inverse(LL a,LL b)
{
LL x,y;
exgcd(a,b,x,y);
return (x%b+b)%b;
}
pa solve(int k,LL n)// 将当前模数的倍数全部提取出来,这样保证a,b互质,就可以用exgcd求逆元 ,对于倍数直接做减法就可以了
{
if (n==0) return make_pair(0,1);
LL x=n/prime[k],y=n/mod[k];
LL ans=1;
if (y)
{
for (LL i=2;in) {
printf("Impossible\n");
return 0;
}
get_prime();
LL ans=work(n,sum)%p;
for (LL i=1;i<=m;i++)
{
ans=ans*work(sum,w[i])%p;
sum-=w[i];
}
printf("%lld\n",(ans%p+p)%p);
}