扩展卢卡斯

-------------------------------------------------------------------------------------------

这是蒟蒻对扩展卢卡斯的一些见解
如有错误欢迎指出,不胜感激

普通卢卡斯                                                                                                                      

快速求出$ C(n,m)$ ($ mod$ p)

约束条件:p为质数

 

考虑扩展                                                                                                                               

预备知识:中国剩余定理(可以参考我的前一篇博客)

我们可以对模数p分解质因数,使得p成为$ {p1}^{k1}{p2}^{k2}... {pn}^{kn}$的形式

列出n个同余方程:

ansx1 ($ mod$ $ {p1}^{k1}$)

ansx2 ($ mod$ $ {p2}^{k2}$)

......

 ansxn ($ mod$ $ {pn}^{kn}$)

由于模数两两互质,因此只要得到每一组的解x1...xn, 就可以通过中国剩余定理合并得到最终的解

 

如何得到每一组的解                                                                                                  

考虑每一组所求的式子为:

$ C(n,m)$ $ mod$ $ {pi}^{ki}$

= $\frac{n!}{m!(n-m)!}$ $ mod$ $ {pi}^{ki}$

由于模数只有一个质因数pi,因此提出先阶乘中的所有pi因子

 

如何求n!中剔除掉pi因子之后数的乘积 $ mod$ $ {pi}^{ki}$                          

下面以$ 22!$ $ mod$ $ 3^2$为例

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

剔除3因子之后

$ (22!)=(1*2*4*5*7*8)*(10*11*13*14*16*17)*(19*20*22)*3^6*(1*2*3*4*5*6*7)$

发现按照如下分组之后前$ \lfloor \frac{n}{pi^{ki}} \rfloor$ 组在$ mod$ $ {pi}^{ki}$ 后结果相同,因此只需要做一组然后快速幂即可

对于$ (19*20*22)$这是冗余,暴力计算即可

对于最后的$ (1*2*3*4*5*6*7)$递归计算即可

3因子被剔除暂不考虑

 

代码:                                                                                              

int Mul(const int n,const int pi,const int pk)//求n! % pi^ki pk=pi^ki
{
    if(n<=1)return 1;ll ans=1;
    if(n>=pk)
    {
        for(rt i=2;i<pk;i++)if(i%pi)ans=ans*i%pk;//计算一组
        ans=ksm(ans,n/pk,pk);//快速幂
    }
    for(rt i=2;i<=n%pk;i++)if(i%pi)ans=ans*i%pk;//计算冗余
    return ans*Mul(n/pi,pi,pk)%pk;//递归求最后一组
}

求出(n!) $ mod$ $ {pi}^{ki}$之后

只需要继续求出$ (m!)$ $ mod$ $ {pi}^{ki}$和$ ((n-m)!)$ $ mod$ $ {pi}^{ki}$,然后对后两项求 $ mod$ $ {pi}^{ki}$的逆元即可

显然剔除pi之后结果一定和$ {pi}^{ki}$互质,所以可以通过扩展欧几里德求逆元

最后加入被剔除的pi的贡献即可

求出pi在$ C(n,m)$中出现的次数k,然后把之前的答案乘上$ pi^k$即可

时间复杂度($ {pi}^{ki}$(求一组的时间)log(n))

注意到对于相同的模数p, 一组的答案相同

因此可以预处理前缀积,然后O(1)的返回每组的答案以及冗余答案

时间复杂度($ {pi}^{ki}$(求一组的时间)+log(n))

最后把所有答案CRT合并即可

 

总复杂度:                                                                                                                              

对于同一模数$ pi^{ki}$:O($ pi^{ki}$)预处理+log(n)求值

全程序复杂度只需要再乘上P的不同质因子个数即可

 

全代码:                                                                                                

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rt register int
#define l putchar('\n')
#define ll long long
#define r read()
using namespace std;
inline ll read()
{
    register ll x = 0; char zf = 1; char ch;
    while (ch != '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}
void write(ll y)
{
    if (y < 0) putchar('-'), y = -y;
    if (y > 9) write(y / 10);
    putchar(y % 10 + '0');
}
inline void writeln(const ll y){write(y);putchar('\n');}
inline int min(const int x,const int y){return x<y?x:y;}
inline int max(const int x,const int y){return x>y?x:y;}
inline ll ksm(ll x,int y,int p)//快速幂 
{
    if(!y)return 1;int ew=1;
    while(y>1)
    {
        if(y&1)y--,ew=x*ew%p;
        else y>>=1,x=x*x%p;
    }
    return x*ew%p;
}
int i,j,k,m,n,x,y,z,cnt,p,P;
void exgcd(int a,int b,int &x,int &y)//扩展欧几里得 
{
    if(!b)x=1,y=0;
    else exgcd(b,a%b,y,x),y-=a/b*x;
}
int inv(int A,int p)//求A mod p意义下的逆元 
{
    if(!A)return 0;int x=0,y=0;
    exgcd(A,p,x,y);x=x%p+p;
    if (x>p)x-=p;return x;
}
ll qz[1000010];//预处理前缀积 
int Mul(const int n,const int pi,const int pk)//求n! % pi^ki pk=pi^ki
{
    if(n<=1)return 1;ll ans=1;
    if(n>=pk)
    {
        ans=qz[pk-1];
        ans=ksm(ans,n/pk,pk);
    }
    if(n%pk)ans=ans*qz[n%pk]%pk;
    return ans*Mul(n/pi,pi,pk)%pk;
}
int C(const int n,const int m,const int pi,const int pk)//求C(n,m)%pk  pk=pi^ki 
{
    qz[0]=qz[1]=1;
    for(rt i=2;i<pk;i++)
    {
        qz[i]=qz[i-1];
        if(i%pi)qz[i]=qz[i]*i%pk;
    }
    int a=Mul(n,pi,pk),b=inv(Mul(m,pi,pk),pk),c=inv(Mul(n-m,pi,pk),pk);
    ll ans=(ll)a*b%pk*c%pk;
    int k=0;//k记录pi出现次数 
    for(rt i=n/pi;i;i/=pi)k+=i;
    for(rt i=m/pi;i;i/=pi)k-=i;
    for(rt i=(n-m)/pi;i;i/=pi)k-=i;
    return ans*ksm(pi,k,pk)%pk;
}
ll ansc;
int main()
{
    for(rt t=read();t;t--)//t组询问,每组询问C(n,m)%p 
    {
        n=read();m=read();p=read();P=p;ansc=0;
        for(rt i=2;i<=p;i++)
        if(p%i==0)
        {
            int pi=i,pk=1;
            while(p%i==0)p/=i,pk*=i;
            (ansc+=(ll)C(n,m,pi,pk)*(P/pk)%P*inv(P/pk,pk))%=P;//CRT合并 
        }
        writeln(ansc%P);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/DreamlessDreams/p/8711503.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值