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;
}
至此,我们已经可以解决原问题了。
完整代码如下
#include<bits/stdc++.h>
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));
}