【知识总结】扩展卢卡斯定理(exLucas)

扩展卢卡斯定理用于求如下式子(其中 p p p不一定是质数):
C n m   m o d   p C_n^m\ mod\ p Cnm mod p
我们将这个问题由总体到局部地分为三个层次解决。

层次一:原问题

首先对 p p p进行质因数分解:
p = ∏ i p i k i p=\prod_i p_i^{k_i} p=ipiki
显然 p i k i p_i^{k_i} piki是两两互质的,所以如果分别求出 C n m   m o d   p i k i C_n^m\ mod\ p_i^{k_i} Cnm mod piki,就可以构造出若干个形如 C n m = a i   m o d   p i k i C_n^m=a_i\ mod\ p_i^{k_i} Cnm=ai mod piki的方程,然后用中国剩余定理即可求解。

层次二:组合数模质数幂

现在的问题就转化成了求如下式子(其中 p p p是质数):
C n m   m o d   p k C_n^m\ mod\ p^k Cnm mod pk
脑补一下组合数公式 C n m = n ! m ! × ( n − m ) ! C_n^m=\frac{n!}{m!\times (n-m)!} Cnm=m!×(nm)!n!,发现由于 m ! m! m! ( n − m ) ! (n-m)! (nm)!可能包含质因子 p p p,所以不能直接求他们对于 p k p^k pk的逆元。此时我们可以将 n ! n! n! m ! m! m! ( n − m ) ! (n-m)! (nm)!中的质因子 p p p全部提出来,最后再乘回去即可。即变为下式( k 1 k1 k1 n ! n! n!中质因子 p p p的次数, k 2 k2 k2 k 3 k3 k3同理):
n ! p k 1 m ! p k 2 × ( n − m ) ! p k 3 × p k 1 − k 2 − k 3 \frac{\frac{n!} {p^{k1}}}{\frac{m!}{p^{k2}}\times \frac{(n-m)!}{p^{k3}}}\times p^{k1-k2-k3} pk2m!×pk3(nm)!pk1n!×pk1k2k3

m ! p k 2 \frac{m!}{p^{k2}} pk2m! ( n − m ) ! p k 3 \frac{(n-m)!}{p^{k3}} pk3(nm)! p k p^k pk是互质的,可以直接求逆元。

层次三:阶乘除去质因子后模质数幂

现在看看如何计算形如下式的式子。
n ! p a   m o d   p k \frac{n!}{p^{a}}\ mod\ p^k pan! mod pk

先考虑如何计算 n !   m o d   p k n!\ mod\ p^k n! mod pk
举个例子: n = 22 n=22 n=22 p = 3 p=3 p=3 k = 2 k=2 k=2
把这个写出来:
22 ! = 1 × 2 × 3 × 4 × 5 × 6 × 7 × 8 × 9 × 10 × 11 × 12 × 13 × 14 × 15 × 16 × 17 × 18 × 19 × 20 × 21 × 22 22!=1\times 2\times 3\times 4\times 5\times 6\times 7\times 8\times 9\times 10 \times 11\times 12\times 13\times 14\times 15\times 16\times 17\times 18\times 19 \times 20 \times 21 \times 22 22!=1×2×3×4×5×6×7×8×9×10×11×12×13×14×15×16×17×18×19×20×21×22

把其中所有 p p p(也就是 3 3 3)的倍数提取出来,得到:

22 ! = 3 7 × ( 1 × 2 × 3 × 4 × 5 × 6 × 7 ) × ( 1 × 2 × 4 × 5 × 7 × 8 × 10 × 11 × 13 × 14 × 16 × 17 × 19 × 20 × 22 ) 22!=3^7 \times (1\times 2\times 3\times 4\times 5\times 6\times 7)\times(1\times 2\times 4\times 5\times 7\times 8\times 10 \times 11\times 13\times 14\times 16\times 17\times 19 \times 20 \times 22 ) 22!=37×(1×2×3×4×5×6×7)×(1×2×4×5×7×8×10×11×13×14×16×17×19×20×22)

可以看出上式分为三个部分:第一个部分是 3 3 3的幂,次数是小于等于 22 22 22 3 3 3的倍数的个数,即 ⌊ n p ⌋ \lfloor\frac{n}{p}\rfloor pn

第二个部分是一个阶乘 7 ! 7! 7!,即 ⌊ n p ⌋ ! \lfloor\frac{n}{p}\rfloor! pn!,可以递归解决

第三个部分是 n ! n! n!中与 p p p互质的部分的乘积,这一部分具有如下性质:
1 × 2 × 4 × 5 × 7 × 8 ≡ 10 × 11 × 13 × 14 × 16 × 17   m o d   p k 1\times 2\times 4\times 5\times 7\times 8\equiv10 \times 11\times 13\times 14\times 16\times 17\ mod\ p^k 1×2×4×5×7×810×11×13×14×16×17 mod pk

在模 3 2 3^2 32的意义下 10 10 10 1 1 1同余, 11 11 11 2 2 2同余……写成下式就比较显然
t t t是任意正整数)

∏ i , ( i , p ) = 1 p k i ≡ ∏ i , ( i , p ) = 1 p k ( i + t p k )   m o d   p k \prod_{i,(i,p)=1}^{p^k}i\equiv\prod_{i,(i,p)=1}^{p^k}(i+tp^k)\ mod\ p^k i,(i,p)=1pkii,(i,p)=1pk(i+tpk) mod pk

∏ i , ( i , p ) = 1 p k i \prod_{i,(i,p)=1}^{p^k}i i,(i,p)=1pki一共循环了 ⌊ n p k ⌋ \lfloor\frac{n}{p^k}\rfloor pkn次,暴力求出 ∏ i , ( i , p ) = 1 p k i \prod_{i,(i,p)=1}^{p^k}i i,(i,p)=1pki然后用快速幂求它的 ⌊ n p k ⌋ \lfloor\frac{n}{p^k}\rfloor pkn次幂。

最后还要乘上 19 × 20 × 22 19\times 20 \times 22 19×20×22(即 ∏ i , ( i , p ) = 1 n   m o d   p k i \prod_{i,(i,p)=1}^{n\ mod\ p^k}i i,(i,p)=1n mod pki),显然这一段的长度一定小于 p k p^k pk,暴力乘上去即可。

如上三部分的乘积就是 n ! n! n!。最终要求的是 n ! p a   m o d   p k \frac{n!}{p^{a}}\ mod\ p^k pan! mod pk,分母全部由上述第一部分和第二部分贡献(第三部分和 p p p互质)。而递归计算第二部分的时候已经除去了第二部分中的因子 p p p,所以最终的答案就是上述第二部分递归返回的结果和第三部分的乘积(与第一部分无关)。

结合代码方便理解:

ll fac(const ll n, const ll p, const ll pk)
{
	if (!n)
		return 1;
	ll ans = 1;
	for (int i = 1; i < pk; i++)
		if (i % p)
			ans = ans * i % pk;
	ans = power(ans, n / pk, pk);
	for (int i = 1; i <= n % pk; i++)
		if (i % p)
			ans = ans * i % pk;
	return ans * fac(n / p, p, pk) % pk;
}

层次二:组合数模质数幂

回到这个式子

n ! p k 1 m ! p k 2 × ( n − m ) ! p k 3 × p k 1 − k 2 − k 3 \frac{\frac{n!} {p^{k1}}}{\frac{m!}{p^{k2}}\times \frac{(n-m)!}{p^{k3}}}\times p^{k1-k2-k3} pk2m!×pk3(nm)!pk1n!×pk1k2k3

可以很容易地把它转换成代码(注意i要开long long):

ll C(const ll n, const ll m, const ll p, const ll pk)
{
	if (n < m)
		return 0;
	ll f1 = fac(n, p, pk), f2 = fac(m, p, pk), f3 = fac(n - m, p, pk), cnt = 0;
	for (ll i = n; i; i /= p)
		cnt += i / p;
	for (ll i = m; i; i /= p)
		cnt -= i / p;
	for (ll i = n - m; i; i /= p)
		cnt -= i / p;
	return f1 * inv(f2, pk) % pk * inv(f3, pk) % pk * power(p, cnt, pk) % pk;
}

层次一:原问题

完整代码(题目:洛谷4720【模板】扩展卢卡斯):

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <climits>
#include <cmath>
using namespace std;
namespace zyt
{
	const int N = 1e6;
	typedef long long ll;
	ll n, m, p;
	inline ll power(ll a, ll b, const ll p = LLONG_MAX)
	{
		ll ans = 1;
		while (b)
		{
			if (b & 1)
				ans = ans * a % p;
			a = a * a % p;
			b >>= 1;
		}
		return ans;
	}
	ll fac(const ll n, const ll p, const ll pk)
	{
		if (!n)
			return 1;
		ll ans = 1;
		for (int i = 1; i < pk; i++)
			if (i % p)
				ans = ans * i % pk;
		ans = power(ans, n / pk, pk);
		for (int i = 1; i <= n % pk; i++)
			if (i % p)
				ans = ans * i % pk;
		return ans * fac(n / p, p, pk) % pk;
	}
	ll exgcd(const ll a, const ll b, ll &x, ll &y)
	{
		if (!b)
		{
			x = 1, y = 0;
			return a;
		}
		ll xx, yy, g = exgcd(b, a % b, xx, yy);
		x = yy;
		y = xx - a / b * yy;
		return g;
	}
	ll inv(const ll a, const ll p)
	{
		ll x, y;
		exgcd(a, p, x, y);
		return (x % p + p) % p;
	}
	ll C(const ll n, const ll m, const ll p, const ll pk)
	{
		if (n < m)
			return 0;
		ll f1 = fac(n, p, pk), f2 = fac(m, p, pk), f3 = fac(n - m, p, pk), cnt = 0;
		for (ll i = n; i; i /= p)
			cnt += i / p;
		for (ll i = m; i; i /= p)
			cnt -= i / p;
		for (ll i = n - m; i; i /= p)
			cnt -= i / p;
		return f1 * inv(f2, pk) % pk * inv(f3, pk) % pk * power(p, cnt, pk) % pk;
	}
	ll a[N], c[N];
	int cnt;
	inline ll CRT()
	{
		ll M = 1, ans = 0;
		for (int i = 0; i < cnt; i++)
			M *= c[i];
		for (int i = 0; i < cnt; i++)
			ans = (ans + a[i] * (M / c[i]) % M * inv(M / c[i], c[i]) % M) % M;
		return ans;
	}
	ll exlucas(const ll n, const ll m, ll p)
	{
		ll tmp = sqrt(p);
		for (int i = 2; p > 1 && i <= tmp; i++)
		{
			ll tmp = 1;
			while (p % i == 0)
				p /= i, tmp *= i;
			if (tmp > 1)
				a[cnt] = C(n, m, i, tmp), c[cnt++] = tmp;
		}
		if (p > 1)
			a[cnt] = C(n, m, p, p), c[cnt++] = p;
		return CRT();
	}
	int work()
	{
		ios::sync_with_stdio(false);
		cin >> n >> m >> p;
		cout << exlucas(n, m, p);
		return 0;
	}
}
int main()
{
	return zyt::work();
}
  • 15
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值