BZOJ 2142 浅谈LuCas EXtra拓展卢卡斯定理解合数组合式

11 篇文章 0 订阅
2 篇文章 0 订阅

这里写图片描述
世界真的很大
今天考试的第一题,讲道理拓展Lucas算NOIP?
还好学会了
考试的时候一看,思路和解法都非常简单,简单到已经不是这道题的主要部分了,求几个组合数就完了,模拟了一下样例过了,很开心
然后一看,嗯?mod怎么是一个合数呢?
EXLucas

看题先:

description:

一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模P后的结果。

input:

输入的第一行包含一个正整数P,表示模;第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。

output:

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

首先一句话谈谈题解,然后浅谈一下拓展卢卡斯
考虑一共要用SUM个礼物,如果比n大的话肯定就是Impossible
不到n的话呢,先枚举从n个里面选SUM个,然后从SUM里面选w1个,从SUM-w1里面选w2个,从SUM-w1-w2里面选w3个,以此类推
写出来就是C(n,SUM) * C(SUM,w1) * C(SUM-w1,w2) 。。。。
而且m只有5,感觉太少了一点,但是的确是这样
问题就出在这个组合数是一个合数。
Lucas定理都是出在mod为质数的情况下。
拓展lucas应运而生


现在来谈一谈拓展Lucas
我们要求的是C(n,m) % mod,mod是一个合数
其实合数的问题主要出在哪里呢?
即求的是 n! / m!/ (n-m)!
如果不是合数,我们采用的方法是处理出m!和(n-m)!的逆元
但是逆元必须要在与mod数互质的情况下才有

对于这一点,我们的处理方法是,把合数mod拆分成如下形式:
P1^a1 * P2^a2 * P3^a3 …………..
其中pi都是质数
考虑对于P1^a1 , P2^a2。。。。他们都是互质的。
考虑对于每一个Pi^ai作为mod数求出C(n,m)的值,就可以用中国剩余定理即孙子定理求出来最后的值
那么现在的问题就变成了求出每一个C(n,m) % Pi^ai的值
即n! / m!/ (n-m)! % Pi^ai
由于我们无法保证n!,m!,(n-m)!与Pi是互质的,所以暂时还是无法使用逆元
既然如此,我们由于Pi^ai的质因数只有有1个Pi,我们就考虑把这阶乘中的Pi全部提出来,变成x * Pi^y的形式
然后x与Pi互质,就可以使用逆元处理,Pi^y在分子和分母上可以通过指数的加加减减得到
即把原式处理成 :
x1 * Pi^y1 / (x2 * Pi^y2 x3 Pi^y3)的形式
那么现在问题就在于如何把n!处理成x * Pi^y的形式

举个例子,20!在mod3^2的意义下处理成 x * 3 ^y
首先先把20!里面含有3的提出来,如3,6,9,12,15,18,一共有6项
提出3^6,那么还剩下6! * 1 * 2 * 4* 5 *7 *8 *10 *11 *13 *14 *16 *17 *19 *20
对于这些数,在mod 3^2的意义下,就存在有循环节,1-9,10-18
每一个循环节乘起来mod 9 的值是相同的,算出一个的值然后快速幂就可以得到
然后还剩下的19和20就暴力算出
6!就变成了20!的子问题,可以递归调用函数求解

中国剩余定理就不讲了,应该都会把,简单版本的


完整代码:

#include<stdio.h>
typedef long long dnt;

dnt mod,n,w[200010],SUM=0;
int m;

dnt quickmub(dnt a,dnt b,dnt m)
{
    dnt rt=1;
    while(b)
    {
        if(b&1) rt=rt*a%mod;
        a=a*a%mod,b>>=1;
    }
    return rt;
}

dnt exgcd( dnt a, dnt b, dnt &x, dnt &y ) 
{
    if(b==0) 
    {
        x=1,y=0;
        return a;
    }   
    dnt x0,y0;
    dnt cd=exgcd(b,a%b,x0,y0);
    x=y0;
    y=x0-(a/b)*y0;
    return cd;
}

dnt inverse( dnt a ,dnt m) 
{
    dnt x, y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}

dnt Divide(dnt x,dnt prm,dnt prk)
{
    if(x==0) return 1;
    dnt tmp=1;
    for(int i=1;i<=prk;i++)
        if(i%prm) tmp*=i,tmp%=prk;
    tmp=quickmub(tmp,x/prk,prk);
    dnt tmp2=x%prk;
    for(int i=2;i<=tmp2;i++)
        if(i%prm) tmp*=i,tmp%=prk;
    tmp=tmp*Divide(x/prm,prm,prk)%prk;
    return tmp%prk;
}

dnt Misaka(dnt a,dnt b,dnt prm,dnt prk)
{
    dnt tmp1=Divide(a,prm,prk);
    dnt tmp2=Divide(b,prm,prk);
    dnt tmp3=Divide(a-b,prm,prk);
    dnt tmp=a,tot=0;
    while(tmp) tot+=tmp/prm,tmp/=prm;
    tmp=b;
    while(tmp) tot-=tmp/prm,tmp/=prm;
    tmp=a-b;
    while(tmp) tot-=tmp/prm,tmp/=prm;
    dnt bns=tmp1*inverse(tmp2,prk) %prk*inverse(tmp3,prk)%prk;
    dnt cns=bns*quickmub(prm,tot,prk)%prk;
    return  cns*(mod/prk)%mod*inverse(mod/prk,prk)%mod;
}

dnt CRT(dnt a,dnt b)
{
    dnt tmp=mod,rt=0;
    for(int i=2;i<=tmp;i++)
    {
        dnt lt=1;
        while(tmp%i==0) tmp/=i,lt*=i;
        if(lt!=1) rt=(rt+Misaka(a,b,i,lt))%mod;
    }
    return rt%mod;
}

int main()
{
    scanf("%lld",&mod); 
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) 
    {
        scanf("%lld",&w[i]),SUM+=w[i];
        if(SUM>n)
        {
            printf("Impossible\n");
            return 0;
        }
    }
    dnt ans=CRT(n,SUM)%mod;
    for(int i=1;i<=m;i++)
        ans=ans*CRT(SUM,w[i])%mod,SUM-=w[i];
    printf("%lld\n",ans);
    return 0;
}
/*
EL PSY CONGROO
*/

嗯,就是这样

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值