// 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;}
预处理的时间复杂度为 O(pα),单次询问的时间复杂度为 O(logpn)。
幂次的计算
本节讨论阶乘 n! 中 p 的幂次 νp(n!) 的计算,它可以用于计算二项式系数的余数。因为二项式系数中,分子和分母都会出现阶乘,而分子和分母中素数 p 能否互相抵消,就成为了决定最后的余数的重要因素。
Legendre 公式
阶乘 n! 中素数 p 的幂次可以通过 Legendre 公式计算,而且与 n 在 p 进制下的表示有关。
Legendre 公式
对于正整数 n,阶乘 n! 中含有的素数 p 的幂次 νp(n!) 为
νp(n!)=i=1∑∞⌊pin⌋=p−1n−Sp(n),
其中,Sp(n) 为 p 进制下 n 的各个数位的和。特别地,阶乘中 2 的幂次是 ν2(n!)=n−S2(n)。
证明
因为
n!=1×2×⋯×p×⋯×2p×⋯×⌊n/p⌋p×⋯×n.
其中,p 的倍数的乘积为 p×2p×⋯×⌊n/p⌋p=p⌊n/p⌋⌊n/p⌋!,而 ⌊n/p⌋! 可能继续出现 p 的倍数。所以,对于幂次,有递推关系:
该表达式可以理解为 p 进制下 m 减掉 n 需要借位的次数。因为如果在计算第 i 位(最低位下标是 1)时存在不够减需要借位的情况,那么相减的结果中第 i 位之前的数字 ⌊pim−n⌋,其实是 m 中第 i 位之前的数字 ⌊pim⌋,减去一(即借掉的一),再减去 n 中第 i 位之前的数字得到的差值 ⌊pin⌋,所以,差值
k=1∑n⌊3k+7(3k+6)!+1−⌊3k+7(3k+6)!⌋⌋=k=1∑n[3k+7 is prime]
参考代码
#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; }}