前置芝士
扩展卢卡斯相对较为复杂,需要较多的前置芝士。
- 快速幂
- 质因数分解
- 组合数公式
- 扩展欧几里得(exgcd)求逆元
- 中国剩余定理(或excrt)
熟练阅读Latex
至于卢卡斯定理,那真的不重要。
问题形式
卢卡斯( )和扩展卢卡斯( )都用于求解形如 的答案。当 是质数时,直接用卢卡斯定理就可以*过去。
如果 不是质数,就得用今天的主角——扩展卢卡斯定理求解。
求解思想
由于
不是质数,那么我们考虑强行对其进行质因数分解:
那么分解完后每一项
之间两两互质,只要我们能得出每组
的答案,就可以用中国剩余定理合并得到最后的答案。
然后来考虑求解
,我们知道
,那么我们也就是要求下面这个式子:
那么显然我们要求出阶乘和阶乘的逆元。由于阶乘与模数当前不互质,所以极有可能不存在直接的逆元,需要我们对式子进行进一步的拆分。
我们从
中把
的倍数全部提出,原式就会变成这样一个式子:
式子中的
可以直接快速幂求出,
考虑递归求解,唯一比较麻烦的是后面剩下的不能被
整除的数。
仔细观察~~(去你丫的)~~可以发现,剩下这些项的乘积有循环节,长度小于 。由于显然 ,所以可以求出共有几个循环节,对第一个循环节暴力求一遍,然后用快速幂搞定。对于最后剩余的不能构成完整循环节的几项,依然是暴力求一遍搞定。
举个经典的例子:
,
,
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ n! &= 1*2*3*4*…
于是我们对括号内的求一遍,然后快速幂,接着暴力求未能构成循环节的
,最后递归求解
。
由于 次这一项比较特殊,因为存在这一项,所以才可能不存在逆元,所以求阶乘的时候我们不对其进行计算,而在求组合数的时候单独处理。也就是说,我们求的阶乘,实际上是去掉了所有因子 后的阶乘。
这一部分的代码:
为了方便,我们会把 当做参数传入到 函数中,即代码中的 。
ll fac(ll n, ll p, ll k) { //n! % k (k=p^a)
if(!n) return 1; //0! = 1
ll ans = 1;
for(int i = 2; i <= k; i++) { //处理循环节内的数字
if(i%p) ans = ans*i % k;
}
ans = qpow(ans, n/k, k); //所有循环节的乘积
for(int i = 2; i <= n%k; i++) { //剩下单独的项
if(i%p) ans = ans*i % k;
}
return ans*fac(n/p, p, k)%k; //递归求解
}
再回到求解组合数的过程。
上面说到,要在求组合数时单独处理形如 的项,那么具体的操作就是:我们用一个 变量记录最后的式子中有多少个因子 ,那么 就等于 的阶乘中含有因子 的数量减去 和 的阶乘中含有的 的数量。
求法就是不断枚举范围内有多少个 的倍数(小学基本功)。
//记得开long long,n很大,直接把int炸飞
for(ll i = p; i <= n; i *= p) cnt += n/i;
for(ll i = p; i <= m; i *= p) cnt -= m/i;
for(ll i = p; i <= n-m; i *= p) cnt -= (n-m)/i;;
然后答案就是 。
这一段的代码:
ll C(ll n, ll m, ll p, ll k){ //k = p^a
if(n < m) return 0;
ll a = fac(n,p,k), b = fac(m,p,k), c = fac(n-m,p,k);
ll cnt = 0;
for(ll i = p; i <= n; i *= p) cnt += n/i;
for(ll i = p; i <= m; i *= p) cnt -= m/i;
for(ll i = p; i <= n-m; i *= p) cnt -= (n-m)/i;
return a*inv(b, k)%k * inv(c, k)%k * qpow(p, cnt, k)%k;
}
接下来就到了正式的 部分。
首先我们把给定的模数 进行质因数分解。
然后对于每个 ,都求一遍组合数 ,然后同时用 合并。
先放 代码:
ll crt(ll n, ll mod){
return n*(p/mod)%p*inv(p/mod, mod)%p;
}
是给定的模数,就相当于 中所有模数的乘积, 是当前的质因数分解出来的模数,就相当于单个方程的模数。
整段 代码:
ll exlucas(){
ll t = p, ans = 0;
for(ll i = 2; i*i <= t; i++){ //质因数分解
if(t%i) continue;
ll tmp = 1;
while(t%i == 0){ //求出pi^ai
tmp *= i;
t /= i;
}
ans = (ans+crt(C(n, m, i, tmp), tmp))%p; //crt合并
}
if(t > 1) ans = (ans+crt(C(n, m, t, t), t))%p; //如果剩下的t是大质数,再进行一次计算
return ans%p;
}
完整代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll n, m, p;
ll exgcd(ll a, ll b, ll &x, ll &y){
if(!b){
x = 1, y = 0;
return a;
}
ll res = exgcd(b, a%b, x, y);
ll t = x;
x = y;
y = t-a/b*y;
return res;
}
ll qpow(ll a, ll n, ll mod){
ll res = 1;
while(n){
if(n&1) res = (res*a) % mod;
a = (a*a) % mod;
n >>= 1;
}
return res;
}
ll inv(ll a, ll p){
ll x, y;
exgcd(a, p, x, y);
if(x+p > p) return x;
return x+p;
}
ll crt(ll n, ll mod){
return n*(p/mod)%p*inv(p/mod, mod)%p;
}
ll fac(ll n, ll p, ll k){ //k = p^x
if(!n) return 1;
ll ans = 1;
for(int i = 2; i <= k; i++){
if(i%p) ans = ans*i % k;
}
ans = qpow(ans, n/k, k);
for(int i = 2; i <= n%k; i++){
if(i%p) ans = ans*i % k;
}
return ans*fac(n/p, p, k)%k;
}
ll C(ll n, ll m, ll p, ll k){ //k = p^x
if(n < m) return 0;
ll a = fac(n,p,k), b = fac(m,p,k), c = fac(n-m,p,k);
ll cnt = 0;
for(ll i = p; i <= n; i *= p) cnt += n/i;
for(ll i = p; i <= m; i *= p) cnt -= m/i;
for(ll i = p; i <= n-m; i *= p) cnt -= (n-m)/i;
return a*inv(b, k)%k * inv(c, k)%k * qpow(p, cnt, k)%k;
}
ll exlucas(){
ll t = p, ans = 0;
for(ll i = 2; i*i <= t; i++){
if(t%i) continue;
ll tmp = 1;
while(t%i == 0){
tmp *= i;
t /= i;
}
ans = (ans+crt(C(n, m, i, tmp), tmp))%p;
}
if(t > 1) ans = (ans+crt(C(n, m, t, t), t))%p;
return ans%p;
}
int main()
{
cin >> n >> m >> p;
cout << exlucas() << endl;
return 0;
}