You don't need to c|

ForgotDream

园龄:2年4个月粉丝:12关注:47

Lucas 定理

组合意义天地灭。

Lucas 定理

问题 1:给定 n,mNpP,其中 nm 相当大,而 p 则相对较小,要求计算 (nm)modp 的值。

一般的预处理逆元以及递推的方法在 n,m 充分大时均会失效,我们需要新的工具来解决这类问题。

对于 n,mNpP,有 (nm)(nimi)(modp),其中 n=iNnipim=iNmipi,也即 nmp 进制表示。

上边的这一坨就是 Lucas 定理了。不过这貌似跟我们平常使用的递归形式不太一样?

对于 n,mNpP,有 (nm)(nmodpmmodp)(npmp)(modp)

这是我们平常见到的形式。不过,稍作观察就可以发现,这两种形式是等价的,在此不做证明。

下文中,如无特殊说明,均有 n,mNpP

下面我们尝试证明 Lucas 定理。

引理 1:对于任意 xNx<p(px)0(modp)

这是好证明的,我们直接从定义下手即可。由于 (px)=p!x!(px)!,又 x<p,因此 x! 中一定没有质因子 p,也即 x!modp 意义下存在逆元 (x!)1。而 (px)! 同理,因此有:

(px)p!(x!)1((px)!)1(modp)

右边显然为 p 的倍数。

引理 2:对于任意 xN,有 (1+x)p1+xp(modp)

通过二项式定理,我们得到 (1+x)p=0ip(pi)xi,而根据引理 1,我们立即得到上式中除去首项 (p0)x0=1 与末项 (pp)xp=xp 外,其余所有项在 modp 意义下均为 0,因此引理得证。

现在来做一些推导:

(1+x)m=(1+x)mpp+mmodp=((1+x)p)mp(1+x)mmodp(1+xp)mp(1+x)mmodp=0imp(mpi)xip0immodp(mmodpi)xi=0imp0jmmodp(mpi)(mmodpj)xip+j(modp)

3 步的转化使用了引理 2。容易看出 ip+j 取遍了所有 [0,m] 的整数,于是考虑枚举 k=ip+j,则有:

(1+x)m0km(mpkp)(mmodpkmodp)xk(modp)

又由二项式定理,我们立即得到:

0km(mk)xk0km(mpkp)(mmodpkmodp)xk(modp)

那么 Lucas 定理是显而易见的。

实际使用中,由于给出的 p 一般不会超过 106,我们通常考虑直接计算 (nmodpmmodp) 的值,而 (npmp) 的值则递归的使用 Lucas 定理进行计算,其边界条件为 m=0 时返回 1。如果预处理所有 i[0,p) 的阶乘与其逆元的话,我们便可以 O(1) 计算前边一部分的值,那么容易得到一个 O(plogp)+O(logpn) 的算法。

扩展 Lucas 定理

这玩意虽然叫定理,但事实上只是个算法。

发现 Lucas 定理只能解决 pP 的问题,而某些时候题目给定的模数不一定是质数,这个时候我们该怎么办呢?

问题 2:给定 n,m,pN,其中 nm 相当大,要求计算 (nm)modp 的值。

由于未保证 p 为质数,普通的 Lucas 定理对此也无能为力。我们考虑将 p 质因数分解,对每一个因子求解后得到一组同余方程,再用 CRT 来合并答案。

对于 pP,有:

(nm)modpk=n!m!(nm)!modpk=n!pg(n)m!pg(m)(nm)!pg(nm)pg(n)g(m)g(nm)modpk

其中 g(x)x! 中质因子 p 的次数。由于 x!pg(x)pk,那么 x!pg(x) 存在 modpk 的逆元,于是我们考虑求出这个式子的值:

n!pg(n)modpk

考虑记上边的式子为 f(n),然后再来做一些推导,我们考虑将 n! 中所有的 p 的倍数提出来,容易看出一共有 nm 个这样的数:

f(n)=n!pg(n)=(pnp1inpi)(1in,i0(modp)i)pg(n)=(pnp(np)!)(1in,i0(modp)i)pg(n)

由于 p 的倍数在 1n 中分布均匀,并且 1pkmodpk 下构成一个完全剩余系,那么后面的积式是有循环节的,那么有:

f(n)=(pnp(np)!)(1in,i0(modp)i)pg(n)(pnp(np)!)(1i<pk,i0(modp)i)npk(npkpkin,i0(modp)i)pg(n)(modpk)

这步变换不难理解。1in,i0(modp)i 根据 modpk 的值可以分为 npk 块,其中前边的块在没有去掉 p 的倍数前均构成 pk 的一个完系,可以用快速幂求得,而最后一块是散块直接暴力累加就行。

下一步的化简就需要用到 g 的性质了。容易注意到 g 的一种递归式:

g(n)=np+g(np)

这非常显而易见,不予证明。那么我们可以将式子作进一步化简:

f(n)(pnp(np)!)(1i<pk,i0(modp)i)npk(npkpkin,i0(modp)i)pg(n)=pnp(np)!pg(n)(1i<pk,i0(modp)i)npk(npkpkin,i0(modp)i)=pnppg(np)f(np)pg(n)(1i<pk,i0(modp)i)npk(npkpkin,i0(modp)i)=f(np)(1i<pk,i0(modp)i)npk(npkpkin,i0(modp)i)(modpk)

于是就做完了。最后再用 CRT 合并一下就可以求得答案。

通过分析可以得到,时间复杂度为 O(pklogpn)

constexpr int N = 1050;
i64 n, m, p;
i64 a[N], b[N];
i64 fastPow(i64 base, i64 exp, i64 mod) {
i64 res = 1;
for (; exp; exp >>= 1) {
if (exp & 1) (res *= base) %= mod;
(base *= base) %= mod;
}
return res;
}
i64 exgcd(i64 a, i64 b, i64 &x, i64 &y) {
if (!b) return x = 1, y = 0, a;
i64 d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
i64 inv(i64 num, i64 mod) {
i64 x, y;
exgcd(num, mod, x, y);
return (x + mod) % mod;
}
i64 f(i64 n, i64 p, i64 pk) {
if (!n) return 1;
i64 cir = 1, rst = 1;
for (i64 i = 1; i <= pk; i++) {
if (i % p) (cir *= i % pk) %= pk;
}
cir = fastPow(cir, n / pk, pk);
for (i64 i = pk * (n / pk); i <= n; i++) {
if (i % p) (rst *= i % pk) %= pk;
}
return f(n / p, p, pk) * cir % pk * rst % pk;
}
i64 g(i64 n, i64 p) {
i64 res = 0, cpy = p;
for (; p <= n; p *= cpy) res += n / p;
return res;
}
i64 C(i64 n, i64 m, i64 p, i64 pk) {
i64 a = f(n, p, pk), b = inv(f(m, p, pk), pk), c = inv(f(n - m, p, pk), pk);
return a * b % pk * c % pk *
fastPow(p, g(n, p) - g(m, p) - g(n - m, p), pk) % pk;
}
i64 exLucas(i64 n, i64 m, i64 mod) {
i64 cpy = mod, res = 0;
int cnt = 0;
for (i64 i = 2; i * i <= cpy; i++) {
if (cpy % i == 0) {
i64 pk = 1;
while (cpy % i == 0) cpy /= i, pk *= i;
a[++cnt] = pk, b[cnt] = C(n, m, i, pk);
}
}
if (cpy != 1) a[++cnt] = cpy, b[cnt] = C(n, m, cpy, cpy);
for (int i = 1; i <= cnt; i++) {
i64 k = mod / a[i], t = inv(k, a[i]);
(res += b[i] * k % mod * t % mod) %= mod;
}
return res;
}

本文作者:forgot-dream

本文链接:https://www.cnblogs.com/forgot-dream/p/17625040.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ForgotDream  阅读(25)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起