「一本通 6.6 练习 8」礼物

Description

原题来自:BZOJ 2142

一年一度的圣诞节快要来到了。每年的圣诞节小 E 都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小 E 心目中的重要性不同,在小 E 心中分量越重的人,收到的礼物会越多。

小 E 从商店中购买了 n 件礼物,打算送给 m 个人,其中送给第 iii 个人礼物数量为 wii​​。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模 PPP 后的结果。

Input

输入的第一行包含一个正整数 P,表示模数;

第二行包含两个正整数 nm,分别表示小 E 从商店购买的礼物数和接受礼物的人数;

以下 mmm 行每行仅包含一个正整数 wii​​,表示小 E 要送给第 iii 个人的礼物数量。

Output

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

Sample Input

100
4 2
1
2

Sample Output

12

Sample Explaination

12 种方案详情如下: {1}{2,3},{1}{2,4},{1}{3,4},{2}{1,3},{2}{1,4},{2}{3,4},{3}{1,2},{3}{1,4},{3}{2,4},{4}{1,2},{4}{1,3},{4}{2,3}

 Hint

P=p1c1×p2c2×p3c3×⋯×ptci pi为质数1c1​​​​×p2c2​​​​×p3c3​​​​××ptct​​​​,pip_ipi​​ 为质数。

对于 100%的数据,1≤n≤109,1≤m≤5,1≤pici≤1059​​,1m5,1pici​​​​105​​。

最近一直在做CRT,乍一看还以为又是一个水题,实际上并没有那么简单。

是时候来总结一下中国剩余定理CRT,并且开启奇妙的扩展卢卡斯定理ex_lucas了。

审题,题意很简单,要求 Π(i=1->m)C(n-∑(j=1->i-1)wj,wj)%P

组合数取模,很容易想到卢卡斯定理。

然而根据题意,P并不一定是质数,普通卢卡斯定理只适用于质数。

怎么办呢?那么你觉得我所说的ex_lucas是干什么用的呢?

如果我们把P质因数分解,不就得到了质数吗?

我们只需要求出ans%piki 然后再CRT解关于它们的余数方程,就好了呀。

  简单介绍CRT:

    对于余数方程xΞai(mod pi) 其中pi彼此互质。

    设P=∏pi,则最小自然数解x=(∑P/pi*ai*si)%P;其中si表示方程(P/pi)*x+pi*y=1的解。

    证明略。而对于pi不互质的情况需要ex_CRT,十分恶心不再这里介绍了。

 1 //tim[i]即为pi,aans[i]即为a[i],prr即为P
 2 int CRT(){
 3     int ans=0,x,y;
 4     for(int i=1;i<=nom;++i){
 5         ex_gcd(prr/tim[i],tim[i],x,y);
 6         x=(x%tim[i]+tim[i])%tim[i];
 7         ans=(ans+prr/tim[i]*aans[i]%prr*x%prr)%prr;
 8     }
 9     return ans;
10 }
CRT

接下来我们继续考虑求ans%piki,下一个问题还是组合数如何对非质数取模。

然而现在这个非质数不再是普通的合数了,而是一个质数的幂,即要求C(n,m)%pk

那么过程中求阶乘时不能很轻易地求出它的逆元了,因为它本身就可能含有p这个因子。

那还不简单?把阶乘的因子p都提出来不就好了嘛?

则C(n,m)%pk=(n! / pk1)*inv(m! / pk2)*inv((n-m)! / pk3)*pk1+k2+k3

现在inv内部的东西与p互质了,可以求逆元了。

接下来再来考虑如何求(n! / pk1)%pk,除个pk1不是什么难事,先讨论n!%pk,如22!%32

=1×2×3×4×5×6×7×8×9×10×11×12×13×14×15×16×17×18×19×20×21×22

提出含p=3的项

=37×(1×2×3×4×5×6×7)×(1×2×4×5×7×8×10×11×13×14×16×17×19×20×22)

蓝色部分:是37,即pn/p,快速幂。

绿色部分:是7!,即(n/p)!,递归解决。

红色部分:是与p互质的数的乘积,其中以pk为分段可以发现前两各完整的段里有

    (1×2×4×5×7×8)Ξ(10×11×13×14×16×17) (mod pk)

    这样就有了一个循环节,快速幂解决。多余部分暴力乘出来就可以。

1 int fac(int n,int p,int pt,int ans=1){
2     if(!n)return 1;
3     for(int i=1;i<pt;++i)if(i%p)ans=ans*i%pt;
4     ans=pow(ans,n/pt,pt);
5     for(int i=1;i<=n%pt;++i)if(i%p)ans=ans*i%pt;
6     return ans*fac(n/p,p,pt)%pt;
7 }
整个n!%pk

逐步回代,解决啦。

 1 #include<cstdio>
 2 #include<cmath>
 3 #define int long long
 4 using namespace std;
 5 int m,pr,sqr,modd[100005],tim[100005],aans[100005],p[100005],nop,num[6],nom,lef[6],prr;bool np[100005];
 6 int pow(int b,int t,int p,int ans=1){
 7     while(t){
 8         if(t&1)ans=ans*b%p;
 9         b=b*b%p;
10         t/=2;
11     }
12     return ans;
13 }
14 int fac(int n,int p,int pt,int ans=1){
15     if(!n)return 1;
16     for(int i=1;i<pt;++i)if(i%p)ans=ans*i%pt;
17     ans=pow(ans,n/pt,pt);
18     for(int i=1;i<=n%pt;++i)if(i%p)ans=ans*i%pt;
19     return ans*fac(n/p,p,pt)%pt;
20 }
21 void ex_gcd(int a,int b,int &x,int &y){
22     if(!b){x=1;y=0;return;}
23     ex_gcd(b,a%b,x,y);
24     int res=x;x=y;y=res-a/b*y;
25 }
26 int inv(int n,int p){
27     int x,y;
28     ex_gcd(n,p,x,y);
29     return (x%p+p)%p;
30 }
31 int c(int b,int t,int p,int pt){
32     if(b<t)return 0;int cnt=0;
33     for(int i=b;i;i/=p)cnt+=i/p;
34     for(int i=t;i;i/=p)cnt-=i/p;
35     for(int i=b-t;i;i/=p)cnt-=i/p;
36     return fac(b,p,pt)*inv(fac(t,p,pt),pt)%pt*inv(fac(b-t,p,pt),pt)%pt*pow(p,cnt,pt)%pt;
37 }
38 int CRT(){
39     int ans=0,x,y;
40     for(int i=1;i<=nom;++i){
41         ex_gcd(prr/tim[i],tim[i],x,y);
42         x=(x%tim[i]+tim[i])%tim[i];
43         ans=(ans+prr/tim[i]*aans[i]%prr*x%prr)%prr;
44     }
45     return ans;
46 }
47 int ex_lucas(){
48     for(int i=1;i<=nop;++i){
49         if(pr%p[i]==0)modd[++nom]=p[i],tim[nom]=1;
50         while(pr%p[i]==0)pr/=p[i],tim[nom]*=p[i];
51     }
52     for(int ii=1;ii<=nom;++ii){
53         aans[ii]=1;
54         for(int i=1;i<=m;++i)aans[ii]=(aans[ii]*c(lef[i],num[i],modd[ii],tim[ii]))%tim[ii];
55     }
56     return CRT();
57 }
58 signed main(){
59     scanf("%lld%lld%lld",&pr,&lef[1],&m);prr=pr;
60     for(int i=1;i<=m;++i)scanf("%lld",&num[i]),lef[i+1]=lef[i]-num[i];
61     if(lef[m+1]<0){puts("Impossible");return 0;}
62     for(int i=2;i<=100000;++i){
63         if(!np[i])p[++nop]=i;
64         for(int j=1;j<=nop&&i*p[j]<=100000;++j)
65             if(i%p[j])np[i*p[j]]=1;
66             else{np[i*p[j]]=1;break;}
67     }
68     printf("%lld\n",ex_lucas());
69 }
详情见代码
posted @ 2019-06-25 11:09  DeepinC  阅读(451)  评论(0编辑  收藏  举报