1 条题解
-
0
相信很多人都想到了 的解法:连着乘。此做法可以查看 @bh1234666 的博客获得更多信息。观察题目数据可以发现:本题需要一个低于 的解法。
我们先设 且 ,那么
$$n!\equiv (\prod_{i=0}^{s-1}f(is))\times\prod_{i=s^2+1}^{n}i\pmod p $$其中 可在 内完成。我们希望快速计算 。
的做法
我们可以使用多项式连续点值平移在 得到 的各项系数,但是通过多项式多点求值得到 需要 的时间复杂度。于是整体时间复杂度为 。
的做法
令 。通过倍增即可求出 。
- 倍增方程1(乘以 )
现在看怎么计算。设 ,则:
- 现有的点值是 (即 )。
- 通过多项式连续点值平移得到 (这里由于要求区间不重叠,会多平移出一个 ,否则会因为除以零寄掉)。
- 将其拼接后得到 (即 )。
- 再次经过平移后得到 $g(0+\dfrac{j}{s}),g(1+\dfrac{j}{s}),\cdots,g(2j+\dfrac{j}{s})$(即 )。
- 乘起来即得 。
- 倍增方程2(加上 )
直接暴力点乘就可以了。
$$\begin{aligned} f_{j+1}(x)&=\prod_{i=1}^{j+1}(x+i)\\ &=\prod_{i=1}^j(x+i)\times(x+j+1)\\ &=f_j(x)\times(x+j+1) \end{aligned} $$然后就可以倍增了。
我们只需要计算 个点值,所以时间复杂度为 。
上代码:
// dill inline int solve(int n, int p) { static vector<int> f, fd, st; st.resize(log2(n) + 5); f.resize(2); int top = 0; int s = n; while (n) { st[++top] = n; n >>= 1; } n = st[top--]; f[0] = 1; f[1] = s + 1; while (top--) { f.resize(f.size() << 1); LagrangeInterpolation_ex(n, n + 1, p, f, fd); std::copy(fd.begin(), fd.end(), f.begin() + n + 1); f[n << 1 | 1] = 0; int tmp = 1ll * n * fpow(s, p - 2, p) % p; n <<= 1; LagrangeInterpolation_ex(n, tmp, p, f, fd); for (int i = 0; i <= n; i++) { f[i] = 1ll * f[i] * fd[i] % p; } if (!(st[top + 1] & 1)) continue; for (int i = 0; i <= n; i++) { f[i] = 1ll * f[i] * (1ll * s * i % p + n + 1) % p; } f[++n] = 1; for (int i = 1; i <= n; i++) { f[n] = 1ll * f[n] * (1ll * s * n % p + i) % p; } } int res = f[0]; for (int i = 1; i < s; i++) { res = 1ll * res * f[i] % p; } return res; } inline int factorial(int n, int p) { int tn = p - 1 - n; int res = 0; if (tn <= n) { res = fpow(factorial(tn, p), p - 2, p); return (tn & 1) ? res : p - res; } int k = sqrt(n); res = solve(k, p); for (int i = k * k + 1; i <= n; i++) { res = 1ll * res * i % p; } return res % p; } signed main() { int T, n, p; cin >> T; while (T--) { cin >> n >> p; cout << factorial(n, p) << endl; } }
习题:
- 1
信息
- ID
- 4209
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 4
- 已通过
- 2
- 上传者