【BZOJ 2142】 礼物

2142: 礼物

Time Limit: 10 Sec   Memory Limit: 259 MB
Submit: 701   Solved: 283
[ Submit][ Status][ Discuss]

Description

一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模P后的结果。

Input

输入的第一行包含一个正整数P,表示模;第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。

Output

若不存在可行方案,则输出“Impossible”,否则输出一个整数,表示模P后的方案数。

Sample Input

100 4 2 1 2

Sample Output

12


【样例说明】
下面是对样例1的说明。
以“/”分割,“/”前后分别表示送给第一个人和第二个人的礼物编号。12种方案详情如下:
1/23 1/24 1/34
2/13 2/14 2/34
3/12 3/14 3/24
4/12 4/13 4/23
【数据规模和约定】
设P=p1^c1 * p2^c2 * p3^c3 * … *pt ^ ct,pi为质数。
对于100%的数据,1≤n≤109,1≤m≤5,1≤pi^ci≤10^5。

组合数取模终极题目。


这道题的答案很好算C(n,tot)+C(tot,w[1])+C(tot-w[1],w[2])+...


由于n很大,想到用lucas定理,但是模不是质数!!


首先质数的幂之间一定互质,那么我们可以把模分解成几个质数的幂的乘积的形式,对每一个质数的幂求解,然后用中国剩余定理将他们合并起来即可。


那么如何快速计算组合数对质数的幂取模呢?


C(n,m)=n!/(m!*(n-m)!)


我们可以把分子分母分别化成t1*p^u1,t2*p^u2的形式,然后把C(n,m)=t1/t2 *p^(u1-u2),此时t1/t2可以用扩展欧几里得直接求出,乘上p^(u1-u2)就是答案。


可是如何快速求出n!=t*p^u的t和u的值呢?


我们把所有是p的倍数的数提出来,先不考虑。


先考虑t的值(注意所有是p的倍数的数不参与计算):

如果y=n/(p^u),如果y>1那么我们可以算出1-p^u的值为ans,那么t=ans^y*剩下数的乘积,否则直接暴力算1-n


再看u的值,p的倍数的数有1*p,2*p,3*p.....k*p(k=n/p),我们把p提出来之后就变成了1,2,3...k,于是问题变成了更小规模的原问题!递归解决即可~

复杂度是线性的~


#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#define N 100000+5
#define LL long long
#define pa pair<LL,LL>
#define mp make_pair
#define fi first
#define se second
using namespace std;
LL mod,a[N];
int n,m,v[N],p[N],s[100],tot,num;
struct divi
{
	int p,c,pc;
}w[N];
void Prepare()
{
	int cnt=0;
	for (int i=2;i<=100000;i++)
		if (!v[i])
		{
			p[++cnt]=i;
			for (int j=i;j<=100000;j+=i)
				v[j]=1;
		}
	num=0;
	LL x=mod;
	for (int i=1;i<=cnt;i++)
	{
		if (x<=1) break;
		if (x%p[i]) continue;
		w[++num].p=p[i];
		w[num].pc=1;
		while (x%p[i]==0)
			w[num].c++,x/=p[i],w[num].pc*=p[i];
	}
}
LL Pow(LL a,int n,LL mod)
{
	LL base=a,ans=1;
	while (n)
	{
		if (n&1) ans=ans*base%mod;
		base=base*base%mod;
		n>>=1;
	}
	return ans;
}
void Exgcd(LL a,LL b,LL &d,LL &x,LL &y)
{
	if (!b)
	{
		d=a,x=1,y=0;
		return;
	}
	Exgcd(b,a%b,d,y,x);
	y-=x*(a/b);
}
LL Inv(LL a,LL n)
{
	LL xx,yy,d;
	Exgcd(a,n,d,xx,yy);
	return (xx%n+n)%n;
}
pa Fac(int k,LL n)
{
	if (n==0) return mp(0,1);
	int x=n/w[k].p,y=n/w[k].pc;
	LL ans=1;
	if (y)
	{
		for (int i=2;i<w[k].pc;i++)
			if (i%w[k].p!=0)
				ans=ans*1LL*i%w[k].pc;
	    ans=Pow(ans,y,w[k].pc);
	}
	for (int i=y*w[k].pc+1;i<=n;i++)
		if (i%w[k].p!=0)
			ans=ans*1LL*i%mod;
	pa p=Fac(k,x);
	return mp(x+p.fi,ans*p.se%mod);
}
LL Get(int k,LL n,LL m)
{
	if (n<m) return 0;
	pa a=Fac(k,n),b=Fac(k,m),c=Fac(k,n-m);
	LL mod=w[k].pc;
	return Pow(w[k].p,a.fi-b.fi-c.fi,mod)*
		   a.se%mod*Inv(b.se,mod)%mod*
		   Inv(c.se,mod)%mod; 
}
LL CRT()
{
	LL M=mod,d,y,x=0;
	for (int i=1;i<=num;i++)
	{
		LL r=M/w[i].pc;
		Exgcd(w[i].pc,r,d,d,y);
		x=(x+r*y*a[i])%M;
	}
	return (x+M)%M;
}
LL C(int n,int m)
{
	for (int i=1;i<=num;i++)
		a[i]=Get(i,n,m);
	return CRT();
}
int main()
{
	scanf("%lld",&mod);
	scanf("%d%d",&n,&m);
	Prepare();
	LL ans;
	int tot=0;
	for (int i=1;i<=m;i++)
		scanf("%d",&s[i]),tot+=s[i];
	if (tot>n)
	{
		puts("Impossible");
		return 0;
	}
	ans=C(n,tot)%mod;
	for (int i=1;i<=m;i++)
	{
		ans=ans*C(tot,s[i])%mod;
		tot-=s[i];
	}
	printf("%lld\n",ans);
	return 0;
}


你可能感兴趣的:(排列组合,OI,bzoj,Lucas定理)