(扩展)卢卡斯定理
卢卡斯定理
结论
其中 \(p\) 为质数。
证明
引理 \(1\)
分子含有质因数 \(p\),则当分母不含有质因数 \(p\) 时原式的值为 \(0\)
否则,当且仅当 \(n = 0\) 或 \(n = p\) 时分母也含有质因数 \(p\),原式的值为 \(\frac{p!}{p! \cdot 0!} = 1\)
引理 \(2\)
根据二项式定理知
根据引理 \(1\) 知 \({p \choose i}\) 在模 \(p\) 意义下不同余于 \(0\),当且仅当 \(i = 0\) 或 \(i = p\),且此时 \({p \choose i}\) 在模 \(p\) 意义下同余于 \(1\)
则有
因为 \(p\) 是质数,由费马小定理知
所以
证明
根据二项式定理知 \((x + 1)^n\) 中的项 \(x^m\) 的系数模 \(p\) 等于 \({n \choose m} \bmod p\)
根据引理 \(2\) 知
易知:
- \((x^p + 1)^{\lfloor \frac{n}{p} \rfloor}\) 中的各项的次数均为 \(p\) 的倍数
- \((x + 1)^{n\ \bmod\ p}\) 中的各项的次数最大为 \(p - 1\)
设 \(m = pq + r\),其中 \(r < q\),则得到 \(x^m\) 只能在 \((x^p + 1)^{\lfloor \frac{n}{p} \rfloor}\) 中取到 \(q\) 个 \(x^p\),然后在 \((x + 1)^{n\ \bmod\ p}\) 中取到 \(r\) 个 \(x\)
又因为 \(q = \lfloor \frac{m}{p} \rfloor, r = m \bmod p\)
所以项 \(x^m\) 的系数模 \(p\) 等于
所以
总时间复杂度 \(\mathcal{O}(\sum\limits_{i = 1}^T n_i \cdot log_2n_i)\),其中 \(T\) 为询问次数
模板
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int t, n, m, p;
ll pre[maxn], inv[maxn];
ll C(ll n, ll m, ll p)
{
if (m > n)
return 0;
return pre[n] * inv[m] % p * inv[n - m] % p;
}
ll lucas(ll n, ll m, ll p)
{
if (m == 0)
return 1;
return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
int main()
{
scanf("%d", &t);
while (t--)
{
scanf("%d%d%d", &n, &m, &p);
pre[0] = pre[1] = 1;
inv[0] = inv[1] = 1;
for (int i = 2; i <= p; i++)
pre[i] = (pre[i - 1] * i) % p;
for (int i = 2; i <= p; i++)
inv[i] = (p - p / i) * inv[p % i] % p;
for (int i = 2; i <= p; i++)
inv[i] = (inv[i - 1] * inv[i]) % p;
printf("%lld\n", lucas(n + m, n, p));
}
return 0;
}
扩展卢卡斯定理
概念
求 \({n \choose m} \bmod p\) 的值,其中 \(p\) 不一定 为质数。
思想
将 \({n \choose m}\) 分解为 \(\prod\limits_{i = 1}^n p_i^{a_i}\) 的形式,因为 \(p_i^{a_i}\) 两两互质,所以可以分别求出 \({n \choose m} \bmod p_i^{a_i}\) 的值,最后再通过中国剩余定理合并。
有 \({n \choose m} = \frac{n!}{m! (n - m)!}\),则问题转化为求 \(\frac{n!}{m! (n - m)!} \bmod p\) 的值。
不妨提出 \(\frac{n!}{m! (n - m)!}\) 中所有含有质因子 \(p\) 的项,得 \(n! = p^{\lfloor \frac{n}{p} \rfloor } \cdot \lfloor \frac{n}{p} \rfloor! \cdot \prod\limits_{p \nmid i}^n i\)
其中 \(p^{\lfloor \frac{n}{p} \rfloor}\) 可以用快速幂求,\(\lfloor \frac{n}{p} \rfloor!\) 可以递归求解,边界条件为 \(0! \bmod p = 1\)
易知 \(\prod\limits_{p \nmid i}^n i\) 中的项的乘积在模 \(p_i^{a_i}\) 意义下有长度小于 \(p_i^{a_i}\) 的循环节,例如:
所以可以暴力求第一个循环节,再通过快速幂求出所有循环节的乘积,最后暴力处理余下的项即可。
模板
#include <cstdio>
using namespace std;
typedef long long ll;
ll n, m, p;
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if (b == 0)
{
x = 1, y = 0;
return a;
}
exgcd(b, a % b, x, y);
ll t = x;
x = y;
y = t - a / b * y;
}
ll fpow(ll a, ll b, ll p)
{
ll res = 1;
while (b)
{
if (b & 1)
res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
ll inv(ll a, ll p)
{
ll x, y;
exgcd(a, p, x, y);
x = (x % p + p) % p;
return x;
}
ll fac(ll n, ll p, ll k)
{
if (n == 0)
return 1;
ll res = 1;
for (int i = 2; i <= k; i++)
if (i % p != 0)
res = res * i % k;
res = fpow(res, n / k, k);
for (int i = 2; i <= n % k; i++)
if (i % p != 0)
res = res * i % k;
return res * fac(n / p, p, k) % k;
}
ll C(ll n, ll m, ll p, ll k)
{
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 * fpow(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 == 0)
{
ll val = 1;
while (t % i == 0)
val *= i, t /= i;
ans = (ans + crt(C(n, m, i, val), val)) % p;
}
}
if (t > 1)
ans = (ans + crt(C(n, m, t, t), t)) % p;
return ans;
}
int main()
{
scanf("%lld%lld%lld", &n, &m, &p);
printf("%lld\n", exlucas());
return 0;
}