【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;
}

时间: 2024-10-12 21:18:59

【BZOJ 2142】 礼物的相关文章

BZOJ 2142: 礼物

2142: 礼物 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 1403  Solved: 578[Submit][Status][Discuss] Description 一年一度的圣诞节快要来到了.每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物.不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多.小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi.请你帮忙计算出送礼物的方案数(两个方

BZOJ 2142 礼物 组合数学+数论

题目大意:给定n个物品,分给m个人,每个人拿到wi个礼物,问方案数mod P P不一定为质数 首先我们把剩下的礼物也分给一个人 答案明显不变 w[++m]=n-w1-w2-...-wm 然后就会很方便地得到公式: ans=C(n,w1)*C(n-w1,w2)*C(n-w1-w2,w3)*...*C(n-w1-w2-...-w_(m-1),wm) mod P =n!/w1!/w2!/.../wm! mod P 然后p不是质数 我们把P分解 令P=∏pi^ai 我们分别处理,可以得到一次同余方程组a

bzoj 2142: 礼物【中国剩余定理+组合数学】

参考:http://blog.csdn.net/wzq_qwq/article/details/46709471 首先推组合数,设sum为每个人礼物数的和,那么答案为 \[ ( C_{n}^{sum}C_{sum}^{w[1]}c_{sum-w[1]}^{w[2]}... \] 设w[0]=n-sum,然后化简成阶乘的形式: \[ \frac{n!}{w[0]!w[1]!...w[n]!} \] 注意到这里p不是质数,所以把p拆成质数的方相乘的形式,最后用中国剩余定理合并即可 然后现在的问题是怎

BZOJ 2142 礼物 数论

这道题是求组合数终极版. C(n,m) mod P n>=1e9 m>=1e9 P>=1e9且为合数且piqi<=1e5 拓展lucas定理. 实际上就是一点数论小知识的应用. 这篇文章对于CRT和lucas定理的学习非常不错. #include<bits/stdc++.h> using namespace std; #define ll long long #define FILE "dealing" #define up(i,j,n) for(i

BZOJ.2142.礼物(扩展Lucas)

题目链接 答案就是C(n,m1) * C(n-m1,m2) * C(n-m1-m2,m3)...(mod p) 使用扩展Lucas求解. 一个很简单的优化就是把pi,pi^ki次方存下来,因为每次分解p都是很慢的. 注意最后p不为1要把p再存下来!(质数) COGS 洛谷上的大神写得快到飞起啊QAQ 就这样吧 //836kb 288ms #include <cmath> #include <cstdio> typedef long long LL; int cnt,P[500],P

bzoj 4827 礼物

bzoj 4827 礼物 可以看做将其中一个数列(假定为 \(a\) )都加上 \(c\) , \(c\) 可以为负数.易知这里 \(-m\leq c\leq m\). 记要求的答案为 \(ans\) , 大力拆开括号可得: \[ ans=\sum{(a_i+c-b_i)^2}\\=\sum a_i^2+\sum b_i^2+n\cdot c^2+2c\cdot (\sum a_i-\sum b_i)-2\sum a_i b_i. \] 这里的 \(a,b\) 是原数列元素不变,通过旋转得到的.

【BZOJ】2142 礼物

[算法]中国剩余定理+组合数取模(lucas) [题意]给定n件物品分给m个人,每人分到wi件,求方案数%p.p不一定是素数. [题解] 首先考虑n全排列然后按wi划分成m份,然后对于每份内都是全排列,除以wi!消除标号影响,注意剩余的(n-W)也视为一份. 所以ans=n!/(w1!w2!...wm!(n-W)!)%p 也可以从排列组合公式方面考虑,即 ans=C(n,w1)*C(n-w1,w2)*C(n-w1-w2,w3)*...*C(n-w1-w2-...-w_(m-1),wm) mod

bzoj 2142

数论大集合 只要你做完了这道题,除了线性筛和降幂公式以外,所有数论noip知识点就都会了... 题意:求C(n,∑w)*C(∑w,w1)*C(∑w-w1,w2).....mod p(不保证p为质数) 思想:拓展卢卡斯定理 算法:我们可以分别求每个C(n,m),然后乘起来mod p即可 在求每个C(n,m)时,由公式C(n,m)= 于是:C(n,m)== 于是我们仅需求出n!mod p的值 可是首先,由于p不是质数,所以不能线性筛逆元 而且,即使p是质数,由于n的范围过大,筛出来也T了 所以我们要

bzoj 3129

非常好的一道数学题,考察了大量数论和组合数学的知识 在做本题之前强烈建议先完成下列两个背景知识: ①: bzoj 2142礼物 因为本题的一部分数据需要利用到拓展卢卡斯定理,而礼物是拓展卢卡斯定理的裸题,先做礼物是一个比较好的选择 有困难戳这里https://blog.csdn.net/lleozhang/article/details/82884768 ②: CF451E 本题的核心思想和CF451E完全相同,CF451E稍简单一些,所以先理解这里的思想再做本题会发现难度降了不少 有困难戳这里