阶乘取模
引入
本文讨论了某一模数下阶乘计算的相关结论,并提供一种时间复杂度线性相关于模数大小的计算方法,因而该方法主要适用于模数不太大(
根据 中国剩余定理,阶乘取模问题可以转化为模数为素数幂
其中,
这种分解在解决阶乘同时出现在所求表达式的分子和分母的问题时尤为有用,比如 计算某一模数下的二项式系数。对于这类问题,分子和分母中
本文还介绍了与上述问题相关的 Wilson 定理及其推广、Legendre 公式和 Kummer 定理等内容。
Wilson 定理
Wilson 定理给出了判断某个自然数是素数的一个充分必要条件。
Wilson 定理
对于自然数
证明
首先,证明对于素数
当
从而,
反过来,对于合数
利用本文的记号,Wilson 定理可以写作
推广
Wilson 定理可以推广到一般模数的情形。
证明
这个定理可以通过 模
对于
因为
对于模
对所有因子
此处的指数
仿照前文的证明思路,可以将所有
这就完成了所有情形的证明。
在计算中,尤为重要的是模数为素数幂的情形:
推论
对于素数
注意,左侧并非
阶乘余数的计算
本节讨论余数
素数模的情形
算式
例子
要计算
可以看出,利用模
将该例子中的递归的结构一般化,就得到如下递推公式:
递推公式
对于素数
证明
记
这就完成了证明。下面对于该形式证明提供具体的解释。
要计算
可以清楚地看到,除了最后一个块外,阶乘被划分为几个长度相同的完整的块。
除了块的最后一个元素外,完整的块的主要部分
总共有
最后一个部分块的值就是
剩下的就是每个块的最后一个元素。如果隐藏已处理的元素,可以看到以下模式:
这也是一个修正的阶乘,只是长度短得多。它是:
将各部分乘起来,就得到上面的递推公式。
利用该递推式做计算,递归深度为
在实现时,因为是尾递归,可以用迭代实现。下面的实现对前
参考实现
// Calculate (n!)_p mod p.
int factmod(int n, int p) {
// Pretreatment.
std::vector<int> f(p);
f[0] = 1;
for (int i = 1; i < p; ++i) {
f[i] = (long long)f[i - 1] * i % p;
}
// Recursion.
int res = 1;
while (n > 1) {
if ((n / p) & 1) res = p - res;
res = (long long)res * f[n % p] % p;
n /= p;
}
return res;
}
如果空间有限,无法存储所有阶乘,也可以将函数调用中实际用到的阶乘
素数幂模的情形
对于素数幂模的情形,可以仿照素数模的情形解决,只需要将 Wilson 定理替换成它的推广形式。本节两个结论中的
证明
证明思路和素数模的情形完全一致。记
与素数模的情形不同之处,除了
在素数模的情形,它退化为
下面提供了在素数幂模的情形下计算阶乘余数的例子,以便理解上述方法:
例子
要计算
将
- 完整的块:由
之间所有不被 整除的整数的乘积,共 块; - 尾部不完整的块:所有不被
整除的整数从 一直乘到 ; - 所有被
整除的整数的乘积,对比倒数第二个等号的结果可知,这就是它的前 项,亦即 。
最后一个括号里的递归求解即可,这样就将原问题转化为了更小的问题。
由此,就可以得到如下递推结果:
递推结果
对于素数
其中,
素数幂模的情形的实现和素数模的情形类似,只有一些细节上的区别。与上文类似,同样可以将预处理放到函数外进行。
参考实现
// Calculate (n!)_p mod pa.
int factmod(int n, int p, int pa) {
// Pretreatment.
std::vector<int> f(pa);
f[0] = 1;
for (int i = 1; i < pa; ++i) {
f[i] = i % p ? (long long)f[i - 1] * i % pa : f[i - 1];
}
// Recursion.
bool neg = p != 2 || pa <= 4;
int res = 1;
while (n > 1) {
if ((n / pa) & neg) res = pa - res;
res = (long long)res * f[n % pa] % pa;
n /= p;
}
return res;
}
预处理的时间复杂度为
幂次的计算
本节讨论阶乘
Legendre 公式
阶乘
Legendre 公式
对于正整数
其中,
证明
因为
其中,
将它展开就得到 Legendre 公式。
要证明第二个等号,首先将
因此,有
求阶乘中素数幂次的参考实现如下:
参考实现
它的时间复杂度为
Kummer 定理
组合数对一个数取模的结果,往往构成分形结构,例如谢尔宾斯基三角形就可以通过组合数模
如果仔细分析,
Kummer 定理
素数
特别地,组合数中
证明
首先证明下面的表达式。为此,利用 Legendre 公式,有
该表达式可以理解为
当且仅当发生了一次借位;否则,该差值为
例题
解题思路
若
设
则
若
设
因此
参考代码
#include <iostream>
constexpr int M = 1e6 + 5, N = 3 * M + 7;
bool not_prime[N];
int sum[M];
int main() {
for (int i = 2; i < N; ++i)
if (!not_prime[i])
for (int j = 2; j * i < N; ++j) not_prime[j * i] = true;
for (int i = 1; i < M; ++i) sum[i] = sum[i - 1] + !not_prime[3 * i + 7];
int t;
std::cin >> t;
while (t--) {
int n;
std::cin >> n;
std::cout << sum[n] << std::endl;
}
}
参考资料
- 冯克勤。《初等数论及其应用》。
- Wilson's theorem - Wikipedia
- Legendre's formula - Wikipedia
本页面主要译自博文 Вычисление факториала по модулю 与其英文翻译版 Factorial modulo p。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。内容有改动。
创建日期: 2025年3月6日