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=i∏piki
设 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} ⎩ ⎨ ⎧ans≡a1(modp1k1)ans≡a2(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!(n−m)!n!modpk
考虑到分子分母都可能含有 p p p 这个质因子,所以不能直接用逆元求。
所以我们同时提出 n ! , m ! , ( n − m ) ! n!,m!,(n-m)! n!,m!,(n−m)! 所有的质因子 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(n−m)!pxn!⋅px−y−zmodpk
现在分子分母都与 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! p⌊pn⌋⋅⌊pn⌋!,因为它们都能提出一个 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!≡p⌊pn⌋⋅⌊pn⌋!⋅ i=1,imodp=0∏i (modpk)
众所周知, x ≡ x + p k ( m o d p k ) x\equiv x+p^k\pmod{p^k} x≡x+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)≡p⌊pn⌋⋅g(⌊pn⌋)⋅ i=1,imodp=0∏pk−1i ⌊pkn⌋⋅ i=1,imodp=0∏nmodpki (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} p⌊pn⌋ 才是 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=0∏pk−1i)⌊pkn⌋⋅(i=1,imodp=0∏nmodpki) 都不是 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=0∏pk−1i ⌊pkn⌋⋅ i=1,imodp=0∏nmodpki (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} Cnm≡f(m)f(n−m)f(n)⋅px−y−z(modpk),如何求 p x − y − z p^{x-y-z} px−y−z 呢?
其实和上面求 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} p⌊pn⌋ 约掉了,而 p ⌊ n p ⌋ p^{\lfloor\frac np\rfloor} p⌊pn⌋ 正是想要求的 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 p⌊pn⌋+p⌊p⌊pn⌋⌋+…。
下方为求 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));
}