BZOJ2142——乘法逆元+中国剩余定理

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≤10^9,1≤m≤5,1≤pi^ci≤10^5。


这道题我想了好久才想透,算是比较复杂的一道数论题。
我们先看题意,要我们求总的方案数,这个方案数其实是挺好推的,因为 n个礼物选出总共sum个,sum里又要选a[1]个,sum-a[1]里选a[2]个。所以我们推出答案为:C(n,sum)*C(sum,a[1])*C(sum-a[1],a[2])*C(sum-a[1]-a[2],a[3])……*C(sum-∑a[1~n-1],a[n])。
这就是我们要求的东西,但是我们要mod数p,如果p是质数,那也十分简单,因为我们直接用Lucas定理求解即可。可这里的p并不是质数。所以我们就得把p分解质因数,然后用中国剩余定理合并(关于怎么合并,我会在接下去讲中国剩余定理的时候介绍)即可。
我们分解质因数,然后用C(n,m)%pi^ki。这里就要用到乘法逆元了:
C(n,m)%pi^ki=n!%pi^ki*inv(m!,pi^ki)*inv(n-m!,pi^ki)。但是如果直接用逆元的话并不可以,因为有可能在这些阶乘中出现取模的数,这样的话就直接变为0了。所以我们要把这些数提取出来。比如6!mod3=1*2*4*5*3^2*1*2,我们对于每一个有3的提出3,然后就可以用逆元了。(注意这里的逆元必须用扩展欧几里得,由于费马小定理必须满足pi为质数或伪素数,但这里是pi^ki)
这里我们要介绍用扩展欧几里得求逆元。逆元的公式为ax≡1(mod m),则我们可以展开即ax-my=1,我们将y取负,即为ax+bm=1,这就可以用扩展欧几里得了。
//扩展欧几里得求逆元
void exgcd(ll a,ll b,ll &x,ll &y,ll &gcd){
    if(b==0){
        x=1;y=0;gcd=a;return;
    }
    exgcd(b,a%b,y,x,gcd);
    y=y-a/b*x;
}
ll inverse(ll a,ll b){
    ll x1,y1,gcd;
    exgcd(a,b,x1,y1,gcd);
    return (x1%b+b)%b;
}
为什么可以用中国剩余定理合并呢?我们要先了解中国剩余定理。
中国剩余定理是求一次同余方程的定理,对于方程:
x≡a1(mod m1), x≡a2(mod m2) …… x≡an(mod mn),我们就可以求出x。
若n1满足n1≡a1(mod m1),且m2~mn|n1,而n2满足n2≡1(mod m2),且除m2的数正处n2……一次类推,这n1到nn全部加起来就是x的一个解。而我们用M=∏mi,则x的解集为{x|x=Mk+∑ni,k∈Z}。
我们用Mi=M/mi,则ni=ai*Mi*inv(Mi,mi),因为这样ni mod mi=ai。
我们可以用一个式子表示:x≡∑ai*Mi*inv(Mi,mi)(mod M)。
这样我们发现,M即为题目中的P,而x也就是我们要求的答案,所以最后mod出来的东西就是中间那一坨,也就是∑ai*Mi*(Mi关于mi的逆元)。
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define pa pair<ll,ll> 
#define fi first
#define se second
using namespace std;
ll read(){
    char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
ll p,n,m,sum,cnt,ans,w[10005],a[10005];
struct node{
    ll p,num,t;
}F[10005];
void fac(ll x){
    for(ll i=2;i*i<=x;i++){
        if(x%i!=0) continue; 
        F[++cnt].p=i;F[cnt].num=1;
        while(x%i==0){x/=i;F[cnt].t++;F[cnt].num*=i;}
    }
    if(x>1){
        F[++cnt]=(node){x,x,1};
    }
}
void exgcd(ll a,ll b,ll &x,ll &y,ll &gcd){
    if(b==0){
        x=1;y=0;gcd=a;return;
    }
    exgcd(b,a%b,y,x,gcd);
    y=y-a/b*x;
}
ll reverse(ll a,ll b){
    ll x1,y1,gcd;
    exgcd(a,b,x1,y1,gcd);
    return (x1%b+b)%b;
}
ll pows(ll a,ll b,ll md){
    ll base=1;
    while(b){
        if(b&1) base=(base*a)%md;
        a=(a*a)%md;b/=2;
    }
    return base%md;
}
pa go(ll pl,ll x){
    if(x==0) return mp(0,1);
    ll u=x/F[pl].p,v=x/F[pl].num,res=1;
    if(v){
        for(ll i=2;i<F[pl].num;i++)if(i%F[pl].p!=0) res=(res*1ll*i)%F[pl].num;
        res=pows(res,v,F[pl].num);
    }
    for(ll i=v*F[pl].num+1;i<=x;i++) if(i%F[pl].p!=0) res=res*1ll*i%p;
    pa tmp=go(pl,u);
    return mp(u+tmp.fi,res*tmp.se%p);
}
ll calc(ll pl,ll x,ll y){
    if(x<y) return 0;
    pa a=go(pl,x),b=go(pl,y),c=go(pl,x-y);
    return pows(F[pl].p,a.fi-b.fi-c.fi,F[pl].num)*a.se%F[pl].num*reverse(b.se,F[pl].num)%F[pl].num*reverse(c.se,F[pl].num)%F[pl].num;
}
ll CRT(){
    ll x=0,y,gcd;
    for(ll i=1;i<=cnt;i++){
        ll r=p/F[i].num;
        exgcd(F[i].num,r,gcd,y,gcd);
        x=(x+r*a[i]*y)%p;
    }
    return (x+p)%p;
}
ll work(ll u,ll v){
    for(ll i=1;i<=cnt;i++)
      a[i]=calc(i,u,v);
    return CRT();
}
int main()
{
    p=read();m=read();n=read();
    for(ll i=1;i<=n;i++) w[i]=read(),sum+=w[i];
    if(sum>m){
        puts("Impossible");return 0;
    }
    fac(p);
    ans=work(m,sum)%p;
    for(ll i=1;i<=n;i++){
        ans=ans*work(sum,w[i])%p;
        sum-=w[i];
    }
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/stevensonson/article/details/79797928
今日推荐