exlucas

exlucas 用来求如下式子
C n m   m o d   p C^m_n\bmod p Cnmmodp
其中 p p p 不一定为质数。


lucas 定理用于求解 p p p 为质数的情况。对于其他情况,容易想到对 p p p 进行质因数分解
p = ∏ i p i k i p=\prod\limits_ip_i^{k_i} p=ipiki
C n m   m o d   p = a n s C^m_n\bmod p=ans Cnmmodp=ans,由中国剩余定理可得模线性方程组:
{ a n s ≡ a 1 ( m o d p 1 k 1 ) a n s ≡ a 2 ( m o d p 2 k 2 ) … \begin{cases} ans\equiv a_1\pmod{p_1^{k_1}}\\ ans\equiv a_2\pmod{p_2^{k_2}}\\ \dots \end{cases} ansa1(modp1k1)ansa2(modp2k2)
若能快速求出 a a a,就能解出 a n s ans ans,得到答案。

现在问题转化为求
C n m   m o d   p k C_n^m\bmod p^k Cnmmodpk
其中 p p p 为质数。


运用组合数公式可得上式为
n ! m ! ( n − m ) !   m o d   p k \dfrac{n!}{m!(n-m)!}\bmod p^k m!(nm)!n!modpk
考虑到分子分母都可能含有 p p p 这个质因子,所以不能直接用逆元求。

所以我们同时提出 n ! , m ! , ( n − m ) ! n!,m!,(n-m)! n!,m!,(nm)! 所有的质因子 p p p,可得
n ! p x m ! p y ( n − m ) ! p z ⋅ p x − y − z   m o d   p k \dfrac{\frac{n!}{p^x}}{\frac{m!}{p^y}\frac{(n-m)!}{p^z}}\cdot p^{x-y-z}\bmod p^k pym!pz(nm)!pxn!pxyzmodpk

现在分子分母都与 p p p 互质,可以用逆元了。但关键是要求 n ! p x   m o d   p k \dfrac{n!}{p^x}\bmod p^k pxn!modpk

先考虑求 n !   m o d   p k n!\bmod p^k n!modpk 吧,好求一点。

这里我们提出 1 1 1 n n n p p p 的倍数,容易得到它们的乘积为 p ⌊ n p ⌋ ⋅ ⌊ n p ⌋ ! p^{\lfloor\frac np\rfloor}\cdot\lfloor\frac np\rfloor! ppnpn⌋!,因为它们都能提出一个 p p p,然后剩下 1 1 1 n p \frac np pn,乘在一起就是 ⌊ n p ⌋ ! \lfloor\frac np\rfloor! pn⌋!


n ! ≡ p ⌊ n p ⌋ ⋅ ⌊ n p ⌋ ! ⋅ ( ∏ i = 1 , i   m o d   p ≠ 0 i ) ( m o d p k ) n!\equiv p^{\lfloor\frac np\rfloor}\cdot\lfloor\frac np\rfloor!\cdot\left(\prod\limits_{i=1,i\bmod p\not=0}i\right)\pmod{p^k} n!ppnpn⌋! i=1,imodp=0i (modpk)

众所周知, x ≡ x + p k ( m o d p k ) x\equiv x+p^k\pmod{p^k} xx+pk(modpk),所以上式后半部分的连乘一定存在若干长度为 p k p^k pk 的循环,循环中的数的乘积相等。

n n n 里面有几个循环呢?为 ⌊ n p k ⌋ \lfloor\frac{n}{p^k}\rfloor pkn

还有 n n n 的末尾不一定是一整个循环,所以要暴力乘上它们。

令函数 g ( n ) = n ! g(n)=n! g(n)=n!,则有
g ( n ) ≡ p ⌊ n p ⌋ ⋅ g ( ⌊ n p ⌋ ) ⋅ ( ∏ i = 1 , i   m o d   p ≠ 0 p k − 1 i ) ⌊ n p k ⌋ ⋅ ( ∏ i = 1 , i   m o d   p ≠ 0 n   m o d   p k i ) ( m o d p k ) g(n)\equiv p^{\lfloor\frac np\rfloor}\cdot g\left(\lfloor\frac np\rfloor\right)\cdot\left(\prod\limits_{i=1,i\bmod p\not=0}^{p^k-1}i\right)^{\lfloor\frac n{p^k}\rfloor}\cdot\left(\prod\limits_{i=1,i\bmod p\not=0}^{n\bmod p^k}i\right)\pmod{p^k} g(n)ppng(pn) i=1,imodp=0pk1i pkn i=1,imodp=0nmodpki (modpk)
在代码实现可使用递归的方式求得 g ( n ) g(n) g(n),注意边界 g ( 0 ) = 1 g(0)=1 g(0)=1

返回来看 n ! p x \dfrac{n!}{p^x} pxn!,发现上面的式子中只有 p ⌊ n p ⌋ p^{\lfloor\frac np\rfloor} ppn 才是 p p p 的倍数,而 ( ∏ i = 1 , i   m o d   p ≠ 0 p k − 1 i ) ⌊ n p k ⌋ ⋅ ( ∏ i = 1 , i   m o d   p ≠ 0 n   m o d   p k i ) \left(\prod\limits_{i=1,i\bmod p\not=0}^{p^k-1}i\right)^{\lfloor\frac n{p^k}\rfloor}\cdot\left(\prod\limits_{i=1,i\bmod p\not=0}^{n\bmod p^k}i\right) (i=1,imodp=0pk1i)pkn(i=1,imodp=0nmodpki) 都不是 p p p 的倍数, f ( ⌊ n p ⌋ ) f\left(\lfloor\frac np\rfloor\right) f(pn) 递归下去也是类似的情况,所以 p x p^x px 全被约掉了。这样,令 f ( n ) = n ! p x f(n)=\dfrac{n!}{p^x} f(n)=pxn!,我们就可以得到一个优美的式子:
f ( n ) ≡ f ( ⌊ n p ⌋ ) ⋅ ( ∏ i = 1 , i   m o d   p ≠ 0 p k − 1 i ) ⌊ n p k ⌋ ⋅ ( ∏ i = 1 , i   m o d   p ≠ 0 n   m o d   p k i ) ( m o d p k ) f(n)\equiv f\left(\lfloor\frac np\rfloor\right)\cdot\left(\prod\limits_{i=1,i\bmod p\not=0}^{p^k-1}i\right)^{\lfloor\frac n{p^k}\rfloor}\cdot\left(\prod\limits_{i=1,i\bmod p\not=0}^{n\bmod p^k}i\right)\pmod{p^k} f(n)f(pn) i=1,imodp=0pk1i pkn i=1,imodp=0nmodpki (modpk)
f ( n ) f(n) f(n) 的代码实现如下:

ll f(ll n,ll p,ll mod)
{
    if(!n) return 1;
    ll ans=1;
    for(int i=1;i<mod;i++) if(i%p) ans=ans*i%mod;
    ans=ksm(ans,n/mod,mod);
    for(int i=1;i<=n%mod;i++) if(i%p) ans=ans*i%mod;
    return ans*f(n/p,p,mod)%mod;
}

求得 f ( n ) f(n) f(n) 后, C n m ≡ f ( n ) f ( m ) f ( n − m ) ⋅ p x − y − z ( m o d p k ) C_n^m\equiv\dfrac{f(n)}{f(m)f(n-m)}\cdot p^{x-y-z}\pmod{p^k} Cnmf(m)f(nm)f(n)pxyz(modpk),如何求 p x − y − z p^{x-y-z} pxyz 呢?

其实和上面求 f ( n ) f(n) f(n) 的思路类似,求 f ( n ) f(n) f(n) 时在 g ( n ) g(n) g(n) 的基础上把 p ⌊ n p ⌋ p^{\lfloor\frac np\rfloor} ppn 约掉了,而 p ⌊ n p ⌋ p^{\lfloor\frac np\rfloor} ppn 正是想要求的 p x p^x px 的一部分。在递归的过程中, p x p^x px 就等于 p ⌊ n p ⌋ + p ⌊ ⌊ n p ⌋ p ⌋ + … p^{\lfloor\frac np\rfloor}+p^{\lfloor\frac{\lfloor\frac{n}{p}\rfloor}{p}\rfloor}+\dots ppn+pppn+

下方为求 C n m   m o d   p k C_n^m\bmod p^k Cnmmodpk 的代码,结合代码方便理解。

ll c(ll n,ll m,ll p,ll mod)
{
    if(n<m) return 0;
    ll f1=f(n,p,mod),f2=f(m,p,mod),f3=f(n-m,p,mod),x=0;
    for(ll i=n;i;i/=p) x+=i/p;
    for(ll i=m;i;i/=p) x-=i/p;
    for(ll i=n-m;i;i/=p) x-=i/p;
    return f1*inv(f2,mod)%mod*inv(f3,mod)%mod*ksm(p,x,mod)%mod;
}

至此,我们已经可以解决原问题了。

题目:P4720 【模板】扩展卢卡斯定理/exLucas

完整代码如下

#include
using namespace std;
typedef long long ll;
ll n,m,mod;
void exgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
    if(!b) d=a,x=1,y=0;
    else exgcd(b,a%b,d,y,x),y-=a/b*x;
}
ll inv(ll n,ll mod)
{
    ll d,x,y;
    exgcd(n,mod,d,x,y);
    return (x%mod+mod)%mod;
}
ll ksm(ll a,ll b,ll mod)
{
    ll ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
ll f(ll n,ll p,ll mod)
{
    if(!n) return 1;
    ll ans=1;
    for(int i=1;i<mod;i++) if(i%p) ans=ans*i%mod;
    ans=ksm(ans,n/mod,mod);
    for(int i=1;i<=n%mod;i++) if(i%p) ans=ans*i%mod;
    return ans*f(n/p,p,mod)%mod;
}
ll c(ll n,ll m,ll p,ll mod)
{
    if(n<m) return 0;
    ll f1=f(n,p,mod),f2=f(m,p,mod),f3=f(n-m,p,mod),x=0;
    for(ll i=n;i;i/=p) x+=i/p;
    for(ll i=m;i;i/=p) x-=i/p;
    for(ll i=n-m;i;i/=p) x-=i/p;
    return f1*inv(f2,mod)%mod*inv(f3,mod)%mod*ksm(p,x,mod)%mod;
}
ll C(ll n,ll m,ll mod)
{
    ll czn=mod,ans=0;
    for(ll i=2;i*i<=czn;i++){
        if(czn%i==0){
            ll x=1;
            while(czn%i==0) czn/=i,x*=i;
            ans=(ans+c(n,m,i,x)*(mod/x)%mod*inv(mod/x,x))%mod;
        }
    }
    if(czn>1) ans=(ans+c(n,m,czn,czn)*(mod/czn)%mod*inv(mod/czn,czn))%mod;
    return ans;
}
int main()
{
    scanf("%lld%lld%lld",&n,&m,&mod);
    printf("%lld",C(n,m,mod));
}

你可能感兴趣的:(c++,算法)