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

· · 题解

题目传送门

算法介绍

exLucas 算法可以求 \dbinom{n}{m}\bmod PP 不一定为质数。(但是,exLucas 实际上和 Lucas 没有多大关系……)

由唯一分解定理,可以对 P 进行质因数分解:

P=p_1^{c_1}p_2^{c_2}p_3^{c_3}\cdots p_n^{c^n}

算法流程

CRT 求解

我们如果能够求出 a_1,a_2,\cdots,a_n 使得:

\begin{cases} \dbinom nm&\equiv a_1\pmod{p_1^{c_1}}\\ \dbinom nm&\equiv a_2\pmod{p_1^{c_2}}\\ &\cdots\\ \dbinom nm&\equiv a_n\pmod{p_n^{c_n}}\\ \end{cases}

那么我们就可以通过 CRT 求解出 \dbinom nm\bmod P。因为在 CRT 中,恰好也有 P=p_1^{c_1}p_2^{c_2}\cdots p_n^{c_n}

求解模质数幂下余数

展开定义式:

\dbinom nm=\frac{n!}{m!(n-m)!}

因此:

\dbinom nm\equiv\frac{n!}{m!(n-m)!}\pmod{p_i^{c_i}}

因为 p_i^{c_i},m!,(n-m)! 不一定互质,因此乘法逆元不一定存在

考虑先提出分子和分母中所有的 p_i 次幂,随后便可以使用逆元求解。

x 的质因数分解中质数 p 的幂次为 \nu_p(x),剩余积为 (x)_p,即:

x=p^{\nu_p(x)}\cdot(x)_p

则有:

\frac{n!}{m!(n-m)!}\equiv\frac{(n!)_{p_i}}{(m!)_{p_i}((n-m)!)_{p_i}}\cdot p_i^{\nu_{p_i}(n!)-\nu_{p_i}(m!)-\nu_{p_i}((n-m)!)}\pmod{p_i^{c_i}}

那么,现在只需要考虑对于 x,p,如何高效地求出 \nu_{p}(x!)\bmod p_i^{c_i},(x!)_p\bmod p_i^{c_i}

考虑:

x!=1\times2\times\cdots\times p\times\cdots\times2p\times\cdots\times\left\lfloor\dfrac{x}{p}\right\rfloor p\times\cdots\times x

容易发现,在 p,2p,3p,\cdots,\left\lfloor\dfrac xp \right\rfloor pp 的个数为 \left\lfloor\dfrac xp\right\rfloor+\nu_p\left(\left\lfloor\dfrac xp \right\rfloor!\right)。其中 \nu_p\left(\left\lfloor\dfrac xp \right\rfloor!\right)1,2,3,\cdots,\left\lfloor\dfrac xp\right\rfloor 中的 p 的个数。

将递推式展开: $$ \begin{aligned} \nu_p\left(x!\right)&=\left\lfloor\dfrac xp\right\rfloor+\nu_p\left(\left\lfloor\dfrac xp \right\rfloor!\right)\\ &=\left\lfloor\dfrac xp\right\rfloor+\left\lfloor\dfrac x{p^2}\right\rfloor+\nu_p\left(\left\lfloor\dfrac xp^2 \right\rfloor!\right)\\ &=\left\lfloor\dfrac xp\right\rfloor+\left\lfloor\dfrac x{p^2}\right\rfloor+\left\lfloor\dfrac x{p^3}\right\rfloor+\cdots \end{aligned} $$ 因此可以 $\mathcal O(\log x)$ 计算 $\nu_p(x!)$: ```cpp int v(ll n,ll p){ int ans=0; do{ n/=p; ans+=n; }while(n); return ans;//这里取不取模其实都无所谓,因为幂次不会太多,想取也可以. } ``` *** 现在考虑如何计算 $(x!)_p\bmod p^c$;显然不能利用定义式 $(x!)_p=\dfrac{x!}{\nu_p(x!)}$,而需要其他方法(因为无法得知 $x!$)。 不难进行递推: $$ \begin{aligned} (x!)_p&\equiv\prod_{i=1}^n(i)_p\\ &\equiv\left(\prod_{1\leq i\leq x,i\perp p}(i)_p\right)\left(\prod_{i=1}^{\left\lfloor\frac xp\right\rfloor}(p\cdot i)_p\right)\\ &\equiv\left(\prod_{1\leq i\leq x,i\perp p}(i)_p\right)\left(\left\lfloor\frac xp\right\rfloor!\right)_p\\ &\equiv\left(\prod_{1\leq i\leq p^c,i\perp p}i\right)^{\left\lfloor\frac{x}{p^c}\right\rfloor}\left(\prod_{1\leq i\leq x\bmod p^c,i\perp p}i\right)\left(\left\lfloor\frac xp\right\rfloor!\right)_p\\ \end{aligned} $$ 由 Wilson 定理的推论($m=2,4,p^c,2p^c$ 时 $k\equiv-1$,否则为 $1$): $$ \prod_{1\leq k\leq m,k\perp m}k\equiv\pm1\pmod m $$ 因此: $$ \begin{aligned} (x!)_p&\equiv(\pm1)^{\left\lfloor\frac{x}{p^c}\right\rfloor}\left(\prod_{1\leq i\leq x\bmod p^c,i\perp p}i\right)\left(\left\lfloor\frac xp\right\rfloor!\right)_p \end{aligned} $$ 可以发现,每次计算 $(x!)_p$ 时,$p^c$ 是固定的,因此可以预处理: $$ f_j\equiv\prod_{1\leq i\leq j,i\perp p}i\pmod p^c $$ 因此,得到最终推导式: $$ \begin{aligned} (x!)_p&\equiv(\pm1)^{\left\lfloor\frac{x}{p^c}\right\rfloor}f_{x\bmod p^c}\left(\left\lfloor\frac xp\right\rfloor!\right)_p \end{aligned} $$ 可以递归或迭代处理,时间复杂度 $\mathcal O(p^c+\log n x)$。 ```cpp int Wilson(int n,int p,int pc){ int ans=1; vector<int>f(pc) f[0]=1; for(int i=1;i<pc;i++){ f[i]=i%p?f[i-1]*i%pc:f[i-1]; } bool flag=p!=2||pc<=4; while(n>1){ if((n/pc)&flag){ ans=pc-ans; } ans=ans*f[n%pc]%pc; n/=p; } return ans; } ``` *** # AC 代码 **一定要开 `__int128`!会爆 `long long`!** ```cpp //#include<bits/stdc++.h> #include<algorithm> #include<iostream> #include<cstring> #include<iomanip> #include<cstdio> #include<string> #include<vector> #include<cmath> #include<ctime> #include<deque> #include<queue> #include<stack> #include<list> using namespace std; typedef long long ll; #define ll __int128 #define int __int128 constexpr const int PMAX=1e6; void exgcd(ll a,ll &x,ll b,ll &y){ if(!b){ x=1; y=0; return; } ll tmp; exgcd(b,tmp,a%b,x); y=tmp-a/b*x; } ll inverse(ll a,ll p){ ll x,y; exgcd(a,x,p,y); x%=p; if(x<0){ x+=p; } return x; } int qpow(int base,int n){ int ans=1; while(n){ if(n&1){ ans=1ll*ans*base; } base=1ll*base*base; n>>=1; } return ans; } int CRT(vector<int>a,vector<int>p){ ll L=1; for(int &i:p){ L*=i; } ll x=0; for(int i=0;i<a.size();i++){ ll q=L/p[i]; x=(x+1ll*a[i]*q%L*inverse(q,p[i])%L)%L; } if(x<0){ x+=L; } return x; } int v(ll n,ll p){ int ans=0; do{ n/=p; ans+=n; }while(n); return ans; } int Wilson(int n,int p,int pc){ int ans=1; vector<int>f(pc); f[0]=1; for(int i=1;i<pc;i++){ f[i]=i%p?f[i-1]*i%pc:f[i-1]; } bool flag=p!=2||pc<=4; while(n>1){ if((n/pc)&flag){ ans=pc-ans; } ans=ans*f[n%pc]%pc; n/=p; } return ans; } void breakDown(int P,vector<int>&p,vector<int>&pc){ int pp=P; for(int i=2;i*i<=pp;i++){ if(pp%i==0){ p.push_back(i); pc.push_back(1); while(pp%i==0){ pc.back()*=i; pp/=i; } } } if(pp>1){ p.push_back(pp); pc.push_back(pp); } } long long exLucas(ll n,ll m,ll P){ if(n<m){ return 0; } vector<int>p,pc; breakDown(P,p,pc); vector<int>a(pc.size()); for(int i=0;i<p.size();i++){ int nWilson=Wilson(n,p[i],pc[i]); int mWilson=Wilson(m,p[i],pc[i]); int nmWilson=Wilson(n-m,p[i],pc[i]); a[i]=nWilson*inverse(mWilson*nmWilson%pc[i],pc[i])*qpow(p[i],v(n,p[i])-v(m,p[i])-v(n-m,p[i])); } return CRT(a,pc); } main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); long long n,m,P; cin>>n>>m>>P; cout<<exLucas(n,m,P)<<'\n'; cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } /* 1000000000000000000 500000000000000000 998243 0 */ ```