[国家集训队]礼物

题目大意:给出n和a[1]到a[m],求∑C(a[i],n-∑a[j](j<i))对非质数P取余的结果。

其实本题难点在于组合数对非质数取余

先了解一下普通lucas

(本人认为仅次于gcd的第二好写的数论板子)

lucas定理常用于组合数对质数取余,定理为:

C(n,m) ≡ C(n/p,m/p) * C(n%p,m%p) ( mod p )

理性理解一下就好。

所以我第一次用lucas+CRT拿了75

接下来进入正题,什么是拓展lucas?

拓展lucas的精髓在于递归地求解 n! % p^k (p是质数) 中国剩余定理 。(说实话和普通lucas没有一点关系

1.求解阶乘对质数整数次幂去模:

先举个例子:

n=22 , p = 3 , k = 2 , pk = p^k = 9

则:

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

其中黑的是p的倍数。

把黑的拉出来,是:3^7 * ( 1 * 2 * 3 * 4 * 5 * 6 * 7 );

其中括号里的是阶乘形式,递归向下做;

然后发现:

1 * 2 * 4 * 5 * 7 * 8 和 10 * 11 * 13 * 14 * 16 * 17 是同余的(mod pk)。

所以可以只算一组(从2到pk),然后快速幂n/pk次。

最后还剩 19 * 20 , 其实只要做 1 * 2就行了,反正同余。

2.CRT的应用:

因为1操作需要pk是p^k形式,所以要把原来的mod分解,然后分组做,可以得到一个线性同余方程组,

然后CRT合并。

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
int P,n,m,a[10];
ll ans = 1;
ll fast(ll x,int y,ll mod)
{
    ll ret = 1ll;
    while(y)
    {
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ret;
}
void exgcd(ll aa,ll b,ll &x,ll &y)
{
    if(!b)
    {
        x=1,y=0;
        return ;
    }
    exgcd(b,aa%b,y,x);
    y-=aa/b*x;
}
ll inv(ll aa,ll b)
{
    ll x,y;
    exgcd(aa,b,x,y);
    x = (x%b+b)%b;
    return x;
}
ll stp(ll x,ll p,ll pk)//x! % p^k
{
    if(!x)return 1;
    ll ret = 1;
    for(int i=2;(ll)i<=pk;i++)
        if(i%p)ret=ret*i%pk;
    ret = fast(ret,x/pk,pk);
    for(int i=2;(ll)i<=x%pk;i++)
        if(i%p)ret=ret*i%pk;
    return ret*stp(x/p,p,pk)%pk;
}
ll C(ll x,ll y,ll M,ll p,ll pk)
{
    ll a = stp(y,p,pk),b = stp(x,p,pk),c = stp(y-x,p,pk),k = 0;
    for(ll i=y;i;i/=p)k+=i/p;
    for(ll i=x;i;i/=p)k-=i/p;
    for(ll i=y-x;i;i/=p)k-=i/p;
    ll ret = a*inv(b,pk)%pk*inv(c,pk)%pk*fast(p,k,pk)%pk;
    return ret*(M/pk)%M*inv(M/pk,pk)%M;
}
ll p0[55],md[55],cnt;
int main()
{
    scanf("%d%d%d",&P,&n,&m);
    int sum = 0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    if(sum>n)
    {
        printf("Impossible\n");
        return 0;
    }
    ll M = P;
    for(int i=2;i*i<=P;i++)
    {
        if(P%i==0)
        {
            p0[++cnt] = i;
            md[cnt]=1;
            while(P%i==0)P/=i,md[cnt]*=i;
        }
    }
    if(P!=1)
    {
        p0[++cnt] = P;
        md[cnt] = P;
    }
    ll las = n,ans = 1;
    for(int i=1;i<=m;i++)
    {
        ll now = 0;
        for(int j=1;j<=cnt;j++)
        {
            now = (now+C(a[i],las,M,p0[j],md[j]))%M;
        }
        ans = ans*now%M;
        las-=a[i];
    }
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LiGuanlin1124/p/9723801.html