1 条题解
-
0
自动搬运
来自洛谷,原作者为

Elma_
blog:https://www.cnblogs.com/came11ia搬运于
2025-08-24 22:35:06,当前版本为作者最后更新于2022-01-24 21:48:55,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
考虑什么样的排列是合法的。不妨设 :
引理 :设 为最小的使得 为 的排列的 , 为最大的使得 为 的排列的 ,那么排列 合法的充分必要条件是 。
我们称 为 “前缀”、 为 “后缀”,那么上面那个条件其实就是: 的前缀和后缀能够覆盖整个排列。
证明:必要性是显然的,假设 ,显然 是一个 的排列,对于这个排列中的数 ,因为 且 ,因此 只可能被这个排列内部的数删除。然而无论我们如何删除,这个排列中都至少会剩下一个数,那么剩下的这个数就永远无法被删掉。
对于 的排列 ,我们尝试给出一种构造的方法使得整个序列能够删到不多于 个数。不妨先考虑前缀:
对于前缀,我们找到最大的 使得 ,对于 的 ,我们分类讨论:
- 若 ,那么由 , 可知 可被 删掉。
- 若 ,那么由 , 可知 可被 删掉。
因此, 的 都可以被删掉。此时,我们再找到使得 最大的 ,再找到最大的 使得 ,重复这个过程直到无法找到新的数,这时 一定是一个 的排列,并且 都可以被删除。
证明:对于一个 ,我们找到最大的 使得 ,那么 就包含了 的所有数。又因为 是满足条件的最大的数,所以如果无法选出新的数,那么 就一定包含了 的所有数,即 是 的一个排列。而对于前缀,它没有更小的前缀是一个排列,因此 中的所有数都是能够被删掉的,后缀同理。又因为前后缀覆盖了整个排列,那么这个排列就是合法的。
现在考虑如何计数。设 表示满足不存在更小的 使得 为排列的 的排列个数。考虑容斥,枚举 的位置,令 是不存在更小的 使得 是 的排列的 的排列,剩下的位置随便放,可以得到式子:
朴素地计算是 的。移项,可以得到:
考虑 的生成函数,令 ,,则有:
那么即有:
多项式求逆即可 计算。
现在考虑如何计算答案,我们还是容斥,计算出不合法的排列个数然后减掉。由于 是固定的,我们枚举最大的 使得 是 的排列。然后对于中间的那一段排列,再枚举最大的 使得 是 的排列,这一段表示不能删掉它们。最后 任意排列。当然,这要求 。
对于固定的 ,设 为 时的方案数,不难写出:
$$h_i = (k-2)!\sum_{j = k}^n f_{j - k+1} f_{n - j + 1} $$后面是一个卷积的形式,令 ,那么:
因此对于固定 的情况,NTT 计算 后就可以 计算 ,答案即为 。但对于不同的 ,我们都必须重新计算答案,因此这个做法的时间复杂度为 ,能获得 分。
现在我们换一种思考的角度:考虑固定 ,计算在这种情况下的答案。设 为长度为 的排列的答案,则:
$$h_n = \sum_{k = p_1 + 1}^{n} (k - 2)!g_{n-k+1} = \sum_{i = 0}^{n-p_1} g_{i}(n-i-1)! $$这也是一个卷积的形式,可以用 NTT 计算。我们发现,当 变为 时, 仅仅会减少一项 ,也就是说,如果我们知道了固定 时 的值,我们可以 得到 时的 。
对于 的情况,我们同样可以按照上述方式计算。于是可以考虑对询问的 分块,我们对 排序,算出 时的答案,每次询问 求出答案。假设 同阶,那么时间复杂度为 ,当 时取最优复杂度 。
然而我的 NTT 常数实在太大了,当 取 的时后大概要跑 次左右的 NTT... 所以这份代码只能获得 分的好成绩。如果之后我卡过了会回来更新的qwq(或者来个神仙教教常数优化/kel)。
Upd:这份代码里的一些点值是可以预处理的,这样就不用每次都 NTT 算一遍了,但大概还是需要 次的 NTT...所以还是没过/kk
int Q, N, M, K, Lim, L, f[MN], g[MN], A[MN], B[MN], G[MN], F[MN], rev[MN], Mx, Frac[MN]; inline int qPow(int a, int b = Mod - 2) { int res = 1; while (b) { if (b & 1) res = res * a % Mod; a = a * a % Mod, b >>= 1; } return res; } inline void NTT(int *a, int Ty) { for (int i = 0; i < Lim; i++) { if (i < rev[i]) swap(a[i], a[rev[i]]); } for (int i = 1; i < Lim; i <<= 1) { int rt = qPow(3, (Mod - 1) / (i << 1)); if (Ty == -1) rt = qPow(rt); for (int j = 0; j < Lim; j += (i << 1)) { int w = 1; for (int k = 0; k < i; k++, w = w * rt % Mod) { int p = a[j + k], q = w * a[j + k + i] % Mod; a[j + k] = (p + q) % Mod, a[j + k + i] = ((p - q) % Mod + Mod) % Mod; } } } if (Ty == -1) { int Inv = qPow(Lim); for (int i = 0; i < Lim; i++) a[i] = (a[i] * Inv % Mod + Mod) % Mod; } } int Inv[MN]; inline void GetInv(int *f, int *g, int Len) { if (Len == 1) return g[0] = qPow(f[0]), void(); GetInv(f, g, (Len + 1) >> 1); for (Lim = 1, L = 0; Lim < (Len << 1); Lim <<= 1) L++; for (int i = 1; i < Lim; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (L - 1)); for (int i = 0; i < Len; i++) Inv[i] = f[i]; for (int i = Len; i < Lim; i++) Inv[i] = 0; NTT(Inv, 1), NTT(g, 1); for (int i = 0; i < Lim; i++) g[i] = ((2 - g[i] * Inv[i] % Mod) + Mod) % Mod * g[i] % Mod; NTT(g, -1); for (int i = Len; i < Lim; i++) g[i] = 0; } #define PI3 pair <pii, int> #define mp3(x, y, z) mp(mp(x, y), z) PI3 q[MN]; int Ans[MN]; inline void Work() { Mx = 1e5; Frac[0] = 1; for (int i = 1; i <= 2 * Mx; i++) Frac[i] = Frac[i - 1] * i % Mod; f[1] = 1, f[0] = 1; for (int i = 2; i <= Mx; i++) f[i] = f[i - 1] * i % Mod; GetInv(f, g, Mx); f[0]--; NTT(f, 1), NTT(g, 1); for (int i = 0; i < Lim; i++) f[i] = f[i] * g[i] % Mod; NTT(f, -1); // for (int i = 0; i < 10; i++) printf("%lld%c", f[i], " \n"[i == 9]); for (int i = Mx + 1; i < Lim; i++) f[i] = g[i] = 0; for (int i = 0; i <= Mx; i++) g[i] = f[i]; NTT(f, 1), NTT(g, 1); for (int i = 0; i < Lim; i++) f[i] = f[i] * g[i] % Mod; NTT(f, -1); // for (int i = 0; i < 10; i++) printf("%lld%c", f[i], " \n"[i == 9]); int B = 1300; sort(q + 1, q + Q + 1, [&](const PI3 &i, const PI3 &j) { return i.fi.se < j.fi.se; }); int o = 1, k = -1; while (o <= Q) { k++; int nB = k * B; if (!nB) nB = 1; for (int i = 0; i < Lim; i++) g[i] = A[i] = G[i] = F[i] = 0; for (int i = 1; i <= Mx; i++) g[i] = f[i]; for (int i = 0; i <= Mx; i++) A[i] = Frac[nB + i - 1]; NTT(g, 1), NTT(A, 1); for (int i = 0; i < Lim; i++) G[i] = g[i] * A[i] % Mod; NTT(G, -1); for (int i = 0; i < Lim; i++) g[i] = A[i] = 0; for (int i = 1; i < nB; i++) g[i] = f[i]; for (int i = 1; i <= Mx; i++) A[i] = Frac[i - 1]; NTT(g, 1), NTT(A, 1); for (int i = 0; i < Lim; i++) F[i] = g[i] * A[i] % Mod; NTT(F, -1); // for (int i = 0; i < 10; i++) printf("%lld%c", g[i], " \n"[i == 9]); // for (int i = 0; i < 10; i++) printf("%lld%c", F[i], " \n"[i == 9]); while (q[o].fi.se < B * (k + 1) && o <= Q) { int c = q[o].fi.se - nB, d = nB, w = G[q[o].fi.fi - nB] + F[q[o].fi.fi]; while (c--) { w = (w - Frac[d - 1] * f[q[o].fi.fi - d] % Mod + Mod) % Mod; w = (w + Frac[q[o].fi.fi - d - 1] * f[d] % Mod) % Mod; d++; } Ans[q[o].se] = (Frac[q[o].fi.fi - 1] - w % Mod + Mod) % Mod; o++; } } } signed main(void) { Q = read(); for (int i = 1; i <= Q; i++) q[i].fi.fi = read(), q[i].fi.se = read(), q[i].se = i; Work(); for (int i = 1; i <= Q; i++) printf("%lld\n", Ans[i]); return 0; }
- 1
信息
- ID
- 7006
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者