洛谷 P3807 【模板】卢卡斯定理

n,m,pn,m,p ,求 Cn+mnmodpC_{n+m}^n\bmod p ,保证 pp 为质数

Lucas 定理,用来求大组合数取模,定理为:

Cnmmodp=Cn/pm/pCnmodpmmodpmodp C_n^m\bmod p=C_{\lfloor n/p\rfloor}^{\lfloor m/p\rfloor}C_{n\bmod p}^{m\bmod p}\bmod p

边界条件:m=0m=0 时,返回 11

时间复杂度:O(f(p)+g(n)logn)O(f(p)+g(n)\log{n}) ,其中 f(n)f(n) 为预处理组合数的复杂度,g(n)g(n) 为单次求组合数的复杂度。

ll lucas(ll n, ll m, ll p){
	if(!m)return 1;
	return (C(n%p,m%p,p)*lucas(n/p,m/p,p))%p;
}

定理证明如下:

首先考虑 CpnmodpC_p^n\bmod p 的值,Cpn=p!n!(pn)!C_p^n=\dfrac{p!}{n!(p-n)!} ,则

Cpnmodp=[n=0n=p] C_p^n\bmod p=[n=0\vee n=p]

从而在 modp\bmod p 下:

(a+b)p=n=0pCpnanbpn=n=0n[n=0n=p]anbpn=ap+bpmodp \begin{aligned} (a+b)^p&=\sum_{n=0}^pC_p^na^nb^{p-n}\\ &=\sum_{n=0}^n[n=0\vee n=p]a^nb^{p-n}\\ &=a^p+b^p \bmod p \end{aligned}

再考虑二项式 f(x)=(axn+bxm)pmodpf(x)=(ax^n+bx^m)^p\bmod p

(axn+bxm)p=apxpn+bpxpm=axpn+bxpmmodp \begin{aligned} (ax^n+bx^m)^p&=a^px^{pn}+b^px^{pm}\\ &=ax^{pn}+bx^{pm} \bmod p \end{aligned}

最后一行是因为费马小定理 ap11modpa^{p-1}\equiv1\bmod p

再考虑二项式 (1+x)nmodp(1+x)^n\bmod p ,则 CnmC_n^m 为其 xmx^m 项前的系数,有:

(1+x)n=(1+x)pn/p(1+x)nmodp=(1+xp)n/p(1+x)nmodp=m=0nCn/pm/pxpm/pCnmodpmmodpxmmodp=m=0nCn/pm/pCnmodpmmodpxpm/p+mmodp \begin{aligned} (1+x)^n&=(1+x)^{p\lfloor n/p\rfloor}(1+x)^{n\bmod p}\\ &=(1+x^p)^{\lfloor n/p\rfloor}(1+x)^{n\bmod p}\\ &=\sum_{m=0}^nC_{\lfloor n/p\rfloor}^{\lfloor m/p\rfloor}x^{p\lfloor m/p\rfloor}C_{n\bmod p}^{m\bmod p} x^{m\bmod p}\\ &=\sum_{m=0}^nC_{\lfloor n/p\rfloor}^{\lfloor m/p\rfloor}C_{n\bmod p}^{m\bmod p} x^{p\lfloor m/p\rfloor+m\bmod p}\\ \end{aligned}

因此 Cnmmodp=Cn/pm/pCnmodpmmodpmodpC_n^m\bmod p=C_{\lfloor n/p\rfloor}^{\lfloor m/p\rfloor}C_{n\bmod p}^{m\bmod p}\bmod p

其中求小范围组合数时用到的乘法逆元可以通过费马小定理求得

#include<iostream>
#include<cstdio>
#define MAXN 100010
using namespace std;
typedef long long ll;
int T,n,m,p;
ll a[MAXN];
ll pow(ll y,int z){
    y%=p;ll res=1;
    while(z){
        if(z&1)res=res*y%p;
        y=y*y%p;z>>=1;
    }
    return res;
}
ll C(ll n,ll m){
    if(m>n)return 0;
    return a[n]*pow(a[m],p-2)%p*pow(a[n-m],p-2)%p;
}
ll lucas(ll n,ll m){
    if(!m)return 1;
    return C(n%p,m%p)*lucas(n/p,m/p)%p;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&m,&p);
        a[0]=1;
        for(int i=1;i<=p;i++)a[i]=(a[i-1]*i)%p;
        printf("%lld\n",lucas(n+m,m));
    }
    return 0;
}

posted @ 2020-06-27 21:19  winechord  阅读(160)  评论(0编辑  收藏  举报