在开始介绍卢卡斯定理之前我们先介绍下面的3个定理
1.乘法逆元
如果ax≡1 (mod p),且gcd(a,p)=1(a与p互质),则称a关于模p的乘法逆元为 x
ax≡1 (mod p) 这个等式用中文描述就是 a乘一个数x并模p等于1,即 a%p*x%p=res,res%p=1
2.费马小定理:如果p是一个质数,而整数a不是p的倍数,即这两个数互质,则有
a^(p - 1) ≡ 1 mod(p) gcd(a, p) = 1
3.拓展欧几里得
已知整数a、b,扩展欧几里得算法可以在求得a、b的最大公约数的同时,
能找到整数x、y(其中一个很可能是负数),使它们满足贝祖等式ax + by = gcd(a, b)
problem_1:为什么我们可以用费马小定理来求得逆元?
证明: 由费马小定理 a^(p - 1) ≡ 1 mod(p) 变形得 ->> a * a^(p - 2) ≡ 1 mod(p)
因为 a, p 互质,a * a^(p - 2) ≡ 1 mod(p) 且 ax≡1 (mod p) 则 x = a^(p - 2) mod(p)
problem_2:为什么我们可以用拓展欧几里得来求得逆元?
证明:法1 我们都知道模后的数就是余数,比如12%5=12-5*2=2,18%4=18-4*4=2。(/是程序运算中的除)
那么ax≡1 (mod p)即ax-yp=1.把y写成+的形式就是ax+py=1,为方便理解下面我们把p写成b就是ax+by=1。
就表示x是a的模b乘法逆元,y是b的模a乘法逆元。然后就可以用扩展欧几里得求了
法2 那么怎么由扩展欧几里得算法求逆元呢?
很简单~
使用条件: a,b为正整数,而且gcd(a,b) = 1
证明:
因为a,b 互质,所以一定有 ax+by = 1
两边同时对b 取余
ax%b + by %b = 1%b -------> ax%b = 1%b
即 ax ≡ 1 (mod b)
扩展欧几里得中x 就是a关于b的逆元
同理y 就是 b 关于 a的逆元
所以使用完欧几里得算法,我们判断返回值d 是否为1,为1说明 gcd(a,b) = 1
即求得的x就是 a关于b的逆元
problem_3:这个逆元有啥子用?
乘法逆元最大的作用就是,在要除以一个数,再取模时,把除法变成乘法运算,然后再取模。
因为除法,比如用16/5应该是3.2,但是计算机会算成3.。。误差有没有,用double就更不用说了,
数大了一定有误差,所以,有了逆元!!!!
(a / b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p // inv代表的是逆元
例如:12 / 5 mod 7 = ?
gcd(g, 7) = 1------->>>>>>> inv(v) = 3
----->>>>>> (12 * 3) % 7 = 1
好了,在了解了上面的知识后,我们进入正题,看一看这个卢卡斯到底什么东西.
定理描述:
k
C(n, m) = ∏ C(ni, mi) (mod p)
i=0
Lucas定理是用来求 c(n,m) mod p,p为素数的值。
证明: C(n,m)%p=C(n/p,m/p)*C(n%p,m%p)%p
C(n, m) mod p = n!/(m!(n - m)!) mod p
当我们要求(a/b)mod p的值,且b很大,无法直接求得a/b的值时,
我们可以转而使用乘法逆元k,将a乘上k再模p,即(a*k) mod p。 其结果与(a/b) mod p等价。
也就是Lucas(n,m)%p=Lucas(n/p,m/p)*C(n%p,m%p)%p
注意:C(N, 0, P) = 1
例题:洛谷: 3807.
Ac 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
ll fac[maxn];
ll n, m, mod;
void init()
{
fac[0] = 1;
for(int i = 1; i <= mod; ++i)
fac[i] = (i * fac[i - 1]) % mod; // 先把mod内的阶乘算出来, 进行预处理
}
ll kpow(ll a, ll b) // 快速幂
{
ll ans = 1;
while(b){
if( b & 1) ans = (ans * a) % mod;
a = (a * a) % mod;
b>>=1;
}
return ans;
}
ll inv(ll x) // 求逆元
{
return kpow(x, mod - 2);
}
ll C(ll n, ll m) //
{
if(n < m) return 0;
return (fac[n] * inv(fac[m] * fac[n - m])) % mod;
}
ll Lucas(ll k, ll b) // 一直进行递归, 直到 进行到这里 -->>> C(N, 0, P) = 1
{
if(b == 0) return 1;
return C(k%mod, b%mod) * Lucas(k/mod, b/mod)% mod;
}
int main(void)
{
int t;
cin>>t;
while(t--){
scanf("%lld%lld%lld", &n, &m, &mod);
init();
printf("%lld\n", Lucas(n, m));
}
return 0;
}