题解 P2183 【[国家集训队]礼物】

___new2zy___

2018-08-15 16:25:31

Solution

# 题解 P2183 [国家集训队]礼物 题目传送门: https://www.luogu.org/problemnew/show/P2183 ========================================== 不得不说这是我做过的最正经的一道黑题了= = 在这里先吐槽一下:~~**数论真恶心**~~ 很是佩服这题的第一个题解,就是有点小蒙,我来给大家讲一下我自己的理解方式 希望同学们能理解本题的核心**数论算法**: **扩展卢卡斯定理**(ExLucas) (这题其实可以当个模板题来做= =) **前置知识**: 扩展欧几里得(Exgcd),乘法逆元, 中国剩余定理(CRT),Lucas定理(不必备) (其实理解了做法也没什么难的对吧)(快逃) ========================================== **简化版题面**: 你手中一共有**n件礼品**,你有**m个好~~(基)~~友**, 你打算**送给每个人wi件礼品**, 请你求出送出礼品的**方案数**,并让**答案对p取模** 请注意,**礼物**之间**两两不同** ========================================== ~~可能本人废话比较多,欢迎来吐槽~~ 看完题目emmm。。。让我冷静一下= = 我们不妨假设**所有朋友收到的礼物数之和sum**: sum=∑(i:1~m)wi 如果记**答案为ans**的话,那么有: ans={C(n,sum)*C(sum,w1)*C(sum-w1,w2)*...*C(wi,wi)}%p 在上式中,C(n,m)代表组合数,每一项组合数乘在一起就是ans 那么**如果n<sum显然无解**,**即"impossible"** (礼物不够当然没法送出去啦QAQ) emmm....继续冷静思考,发现好像很简单?(逃 ~~(只要**暴力递推计算**就好啦)~~ 但是,如果我们**直接递推暴力求解ans**的话,那么会发现**时间复杂度不能接受**: 题目中给定的n的范围是1e9,预处理时直接递推求阶乘的话估计就要跑个很长时间吧= =所以这种做法显然是不可取的 那么这样就很难受,继续思考= = "n很大,还要求组合数取模,那么用**Lucas定理吧**" 灵机一动,想到在数论问题中,对于类似于 C(n,m)%p(**必须保证p为prime**)的问题, 我们有一个定理:**Lucas定理** 这个定理大概长这个样子: **Lucas(n,m)=C(n%p,m%p)*Lucas(n/p,m/p)** 其中Lucas(n,m)其实就是要计算的式子C(n,m)%p 对于这个定理,在此就不证明了~~(我比较菜所以不会证明)~~ **(注意:Lucas定理适用于n<=1e5)** 回到这题上来。。。 读者会发现,,好像我之前说的是废话= = 这个题n<=1e9好吧!!你在逗我?没法做。。 先淡定一下,到了这里其实已经离正解不远了,毕竟求解有了一定的方向 再次陷入沉思= =发现我们的**Lucas定理不可以直接使用**,因为题目中还有一个很致命的**限制条件**: p<=1e9(此外什么也没说) 那么就抛来一个很重要的问题:**模数p不保证是质数** 那么导致我们如果**直接用逆元取模**的话不行,因为可能在这些阶乘中出现取模,这样答案直接就变为0了,因此我们要尝试把这些数提取出来。 emmm。。。恐怖如斯= =根本没法做了啊 但是我们发现,题目中给了提示: 设P=p1^c1*p2^c2*p3^c3*…*pt^ct,pi为质数。 其实这是数论中的**唯一分解定理**: 任意大于1的正整数N,存在唯一分解式N=p1^c1*p2^c2*...*pi^ci 其中p1~pi是质数,^是次方,c1~ci是次数 那么又可以很自然的想到: 我们可以将分子分母都对于p的唯一分解式中的每一项(即pi^ci)取模(就保证是prime了),最后将每一项用CRT合并就得到了解 我们不妨再来考虑一下**组合数取模的计算公式**: **C(n,m)%p={fac(n)/[fac(m)*fac(n-m)]}%p** (注意,这里只是形式,实际上除法不能直接取模) 其中fac(x)表示x的阶乘,即x! 如果拆分的话,我们可以对于n!,m!,(n-m)!分别%p再进行合并即可求出答案 又可以注意到,其实整个问题就剩下了一个式子: 求解(x!)%(pi^ci),保证pi为质数 对于这个式子展开可以发现,它的**求解分为三部分**: 1. 在x!中,若存在pi的倍数那么就可以拆出来,与pi^ci抵消,即可以转化为**子问题**:求**(x/pi)!%pi^ci**,只需要**递归**下去**求解**就行了 2. n!中可能有会包含多个完整的1~pi^ci-1(在%pi^ci下的剩余系),这部分可以先预处理一下(pi^ci-1)! (注意是不包含pi的倍数的阶乘,因为我们还要记录一下阶乘中pi的次数最后一起处理),然后pi的若干次幂用快速幂处理。 3. 那么进行完1,2两步之后,对于剩下的部分直接求一发逆元计算组合数,最后合并即可(扩欧求逆元,中国剩余定理合并) 有些懵逼?我来举个栗子食用更佳哈: 假设pi=5,展开阶乘可以发现: n!= (1*2*3*4*6*7*8*9*11*…)*5^(n/5)*(n/5)! (乘号*没了555,变成了斜体请自行脑补) 那么我们直接提取出所有pi=5的倍数,正好分为三部分,直接按步骤上面计算就行了 其实以上就是~~(最恶心的)~~**数论算法:扩展卢卡斯定理(ExLucas)** 讲到这里。。。突然发现这东西好像和卢卡斯定理一点关系没有= =(所以Lucas不是必备的知识) 大概思路讲完了,我们在放代码之前可以来总结一下这次的求解方法,即**扩展卢卡斯定理(ExLucas)** **扩展卢卡斯定理适用于计算任意的C(n,m)%p问题,其中n,m,p为任意正整数** 求解过程主要应用了**乘法逆元**(**扩欧Exgcd**求)、**中国剩余定理**(CRT也就只有一行)、带取模**快速幂**(码量很小) 总体来说思路还是很清楚的吧 那么好啦,既然有了解决方案,题目里的式子就很好求了吧,放个代码code~~~ PS:代码里也有解释哦 ```cpp #include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<queue> #include<cstring> using namespace std; typedef long long ll; const int inf=1e9+7; inline ll poww(ll a,ll b,ll mod)//快速幂 { ll base=a,ans=1; while(b) { if(b&1) ans=(1ll*ans*base)%mod; base=(1ll*base*base)%mod; b>>=1; } return 1ll*ans; } inline ll read()//快读 { ll p=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();} return 1ll*f*p;} ll n,m,sum,mod,ans=1,A[19]; inline void Exgcd(ll a,ll b,ll &x,ll &y) //扩欧用来求逆元 { if(!b){x=1,y=0;return ;} Exgcd(b,a%b,y,x);y-=a/b*x; } inline ll rev(ll k,ll p) //求k在mod p下的逆元(转换一下变成正整数) { if(!k)return 0; ll x=0,y=0,a=k,b=p; Exgcd(a,b,x,y); x=(x%b+b)%b; if(!x)x+=b; return 1ll*x; } inline ll mul(ll n,ll p,ll pk) //求n!%pi^ci,pk=pi^ci { if(!n)return 1; ll ans=1; for(ll i=2;i<=pk;i++) if(i%p)ans=ans*i%pk; ans=poww(ans,n/pk,pk); for(ll i=2;i<=n%pk;i++) if(i%p)ans=ans*i%pk; return 1ll*ans*mul(n/p,p,pk)%pk; //递归下去求解(n/pi)!%pi^ci } inline ll C(ll n,ll m,ll mod,ll p,ll pk) //求C(n,m)%mod,其中唯一分解之后质因子为p,总乘积为pk(pi^ci) { if(m>n)return 0; ll a=mul(n,p,pk),b=mul(m,p,pk),c=mul(n-m,p,pk),k=0; //先求一下n!%pi^ci,m!%pi^ci,(n-m)!%pi^ci for(ll i=n;i;i/=p)k+=i/p; for(ll i=m;i;i/=p)k-=i/p; for(ll i=n-m;i;i/=p)k-=i/p; //先除掉n!,m!,(n-m)!在%mod下的质因子p ll ans=1ll*a*rev(b,pk)%pk*rev(c,pk)%pk*poww(p,k,pk)%pk; //除去质因子p之后直接逆元求组合数(剩余部分) return 1ll*ans*(mod/pk)%mod*rev(mod/pk,pk)%mod; //找到逆元了再乘回去(CRT合并) } int main() { /*这是我在做这个题的时候写的解释 思路:不妨设sum=∑wi(i:1~m) 题目很简单,就是要求式子: C(n,sum)*C(sum,w1)*C(sum-w1,w2)*...*C(wi,wi) 但组合数要取模 自然想到逆元和Lucas定理 但是模数p不保证是质数,所以要用扩展Lucas 不妨先把p唯一分解,对于每一个pi^ci都两两互质 可以求出C(n,m)%pi^ci 对于求这个,除掉质因子pi然后求逆元 之后CRT一下乱搞就好啦 */ mod=read(),n=read(),m=read(); for(int i=1;i<=m;i++) A[i]=read(),sum+=A[i]; if(sum>n)//要送出的礼物多于有的礼物显然无解 { printf("Impossible"); return 0; } for(ll k=1;k<=m;k++)//枚举每一个人要的礼物 { n-=A[k-1]; ll now=0,x=mod; for(ll i=2;i<=sqrt(mod);i++) //找到mod的每一个质因数p if(!(x%i)) { ll pk=1; while(!(x%i))pk*=i,x/=i;//除掉质因数p now=(now+C(n,A[k],mod,i,pk))%mod; //求出C(n,A[k])%pi^ci累加 } if(x>1)now=(now+C(n,A[k],mod,x,x))%mod; ans=ans*now%mod;//统计答案 } printf("%lld",ans);//愉快的输出答案 return 0; } ``` 好了,到这里大概就讲完了吧= = 真香,又是一道毒瘤呢(逃 感谢阅读~~~ 最后来推广一下我的blog: https://www.luogu.org/blog/new2zy/ 拜拜~~~ >=<