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

虞皓翔
Eternal dominant seventh.搬运于
2025-08-24 22:10:34,当前版本为作者最后更新于2019-07-03 22:08:46,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
鉴于这题已经过了三年且网上没有靠谱题解,
以及笔者写本文时所有 luogu AC 代码均是本人的代码,现在这里将简单介绍一下思路,起些抛砖引玉的作用。如果要详细的题解 (比如细节等),请左转 此处 以获得更好(cha)的阅读体验~
我们分 7 步来完成这道题。
---- Step 1 ----
首先考虑对于一个长度为 的串 和后缀数组 ,什么时候是合法的。
我们在 的末端加一个字典序最小的字符,并令 。
于是, 是 的后缀数组的充要条件是,如果 在 的后面,则 ;如果 也在 的前面,则要有 。
---- Step 2 ----
接下来我们就能推出,对于一个确定的后缀数组,怎样的串是满足条件的。
由 Step 1 的结论,所有 会按照一定的顺序形成一条形如 $s_{p_1} \otimes s_{p_2} \otimes \cdots \otimes s_{p_n}$ 的不等式链,其中 为 或 。
举个例子:后缀数组
5 3 1 4 2形成的不等式链即为 。
---- Step 3 ----
我们先从最简单的情形着手,。(注:此处默认 )
为了防止计算重复,我们对于一个后缀数组 ,定义它的秩为最小的 ,满足存在字符集大小为 的字符串,使其后缀数组恰好为 。
因此一个后缀数组的秩等于它在 Step 2 的不等式链中 "" 号的个数。
我们需要统计字符集大小为 的 SA 的个数,不妨先来统计秩为 的 SA 的个数。
设字符串中有 个
a, 个b。因此将它们进行带重复元素的排列,可知共有 种情况。我们要从中去掉秩为 的情况。易知,秩为 的后缀数组恰有一个 (其实就是 ),因此由 个
a, 个b构成的字符串中,秩为 的后缀数组的个数就是 。
---- Step 4 ----
经历完 的情况,再来考虑 的情况。
还是先统计秩为 的后缀数组个数。设其中有 个
a, 个b以及 个c。由可重排列,由这些元素构成的秩不超过 的不同 SA 个数共有 个。
当然,这些是秩不超过 的。我们还是要将这些秩过小的排列 "容斥" 掉。
回到 Part 2 中的不等式链。首先,由于这些 SA 的秩不超过 ,因此不等式链中严格小于号 "" 的个数不会超过 。
考察所有 个排列中的一个排列 ,设它的秩为 。由于它是我们在统计秩为 的排列中计入的,因此它的不等式链的形式本应该是:
$$x_1 \leq x_2 \leq \cdots \leq x_A {\color{red} {}<{}} x_{A + 1} \leq x_{A + 2} \leq \cdots \leq x_{A + B} {\color{blue} {}<{}} x_{A + B + 1} \leq x_{A + B + 2} \leq \cdots \leq x_{A + B + C} \qquad \left( 1 \right) $$而事实上,它的秩可以是 。也就是说,实际上,不等式链中只有 个严格小于号。
然而 式中的等号是必定取等的,因此这个唯一的小于号要么在红色位置,要么在蓝色位置。
不妨设 "" 号在蓝色位置,则红色位置上的 就可以取等了。
也就是说,此时,这个后缀数组即为满足 $s_{p_1} = s_{p_2} = \cdots = s_{p_{A + B}} = \texttt a, \ s_{p_{A + B + 1}} = s_{p_{A + B + 2}} = \cdots = s_{p_{A + B + C}} = \texttt b$ 的串 的后缀数组。
因此,每个 "由 个 , 个 构成的串" 的后缀数组,都会对我们的统计造成一个 "误判"。因此,我们要答案减去 。
同理,"" 也可以在红色位置,此时则需要减去 。
可以看出,这是一个容斥的过程。因此,我们还需要把秩为 的后缀数组补回来。此时,我们不再在式子中写 (恰有 个秩为 的后缀数组),而是:
$$tot = \binom {A + B + C} {A, B, C} - \binom {A + B + C} {A + B, C} - \binom {A + B + C} {A, B + C} + \binom {A + B + C} {A + B + C} \qquad \left( 2 \right) $$
---- Step 5 ----
然鹅题目不是让你统计秩为 的 SA 的个数!
那我们刚才搞那么多到底是为了啥?无非就是构造一个一一对应,将一个排列对应到一个串上去,使得计数不重不漏啊!
因此,对一个秩为 的排列,我们就应当恰好在统计秩为 为排列中统计进去!
具体地,我们按字典序从小到大枚举每个字符出现了几次,然后算出有多少个 "满秩" 的后缀数组。(这里 "满秩" 的意思是,这个串的字符集大小恰好等于该后缀数组的秩,不出现字符集冗余的情况)
但是这里还有一个比较关键的问题。有的秩比较小的后缀数组,它的来源是一个较大的字符集,而它所对应的 "满秩" 的字符串是不符合题目要求的。
举个例子,你有 个 和 个 ,则 的后缀数组为 ,秩为 ,而所对应的 "满秩" 字符串 或 均不满足题目要求 (任意字符你只有 个)。
因此我们需要一点计数技巧,在这里可以采用 "合并" 这种操作。
考虑 从小到大填充的过程。如果一个字符,比如 ,出现了超过 次。首先,它肯定达到了 次。因此,一旦它达到了 次,我们就可以将 的 次使用机会全都 "借" 给
(反正也是有借无还嘛),从而完成 "合并" 的过程。但是,能借后面的串用的前提是, 先把自己的 次机会用完。
当然,不仅是 ,我们对待每个字符都是一视同仁的,只有把自己的机会用完了,才能 "借" 用下一个字符的机会。
---- Step 6 ----
最后就只剩下实现了,至于如何高效地完成这个容斥的过程,那就考虑用 DP 来求解容斥系数啦。
将 式推广,设第 小的字符有 个。则整个容斥的过程可以看成枚举断点集合 ("" 集合) ,则这一部分的贡献为
$$(-1)^{m - k} \binom {A_1 + A_2 + \cdots + A_m} {A_1 + A_2 + \cdots + A_{z_1}, A_{z_1 + 1} + \cdots + A_{z_2}, \cdots, A_{z_k + 1} + \cdots + A_{z_m}} \qquad \left( 3 \right) $$首先,分子上的 是常量,可以预先提出来。然后对于每个长度为 ,对最终结果有一个阶乘倒数 的贡献。
(下面就是 DP 状态定义啦)
用 表示当前从小到大填充到第 个字符,已经填充了 个位置 (i.e. 当前 的前缀和为 ),最后一段的大小为 (i.e. ),所得到的容斥系数。
要注意的一点是,注意,这个 DP 是基于贪心的,因此每个字符一定是能用则用,因此不使用的字符一定是一段后缀,即一旦一个字符未使用,就预示着这个串的 "终结"。
先扔掉 的字符,边界是 (当然也可以是 ,个人习惯)。
转移分三种情况:
- 第 个字符使用若干 (正整数) 个,然后将这一段切掉 (也是最正常的情况)。
设这种字符使用了 个,则转移为
- 第 个字符准备和下一个字符进行容斥型合并,即它们在 式中使用加号产生贡献。
由于是容斥型合并,每多一个 "" 号就要产生 的系数。故转移方程为
- 第 个字符准备和下一个字符进行正常合并 (即统计秩比较小的后缀数组)。由 Step 5,此时要求第 个字符必须用满。
因此转移系数还是 ,方程为
以上 表示
a += b(in C++)。至于统计答案,相当于统计这个串在哪个字符处 "终结",即答案等于
时间复杂度 。
---- Step 7 ----
最后一步相信大家都明白,对这个 DP 进行优化。
不难发现,当固定 时,这个 DP 方程其实就得是一个对角线方向上的区间和!
因此,可以使用前缀和优化来解决,不过要注意一下求和的上下限。
于是单点转移就被优化到了 ,总时间复杂度 ,就可以通过了。
代码:
#include <bits/stdc++.h> #define jl j #define kl k typedef long long ll; const int N = 540, mod = 1000000007; int n, m; int c[N], fact[N], finv[N]; int f[N][N], F[N][N]; inline void add(int &x, const int y) {x = (x + y >= mod ? x + y - mod : x + y);} ll PowerMod(ll a, int n, ll c = 1) {for (; n; n >>= 1, a = a * a % mod) if (n & 1) c = c * a % mod; return c;} int main() { int i, j, k, l; ll ans = 0; scanf("%d%d", &n, &m); for (i = 1; i <= m; ++i) if (scanf("%d", c + i), !c[i]) --i, --m; for (*fact = i = 1; i <= n; ++i) fact[i] = (ll)fact[i - 1] * i % mod; finv[n] = PowerMod(fact[n], mod - 2); for (i = n; i; --i) finv[i - 1] = (ll)finv[i] * i % mod; for (**f = i = 1; i <= m; ++i) { for (j = 0; j <= n; ++j) for (k = 0; k <= j; ++k) add(F[j + 1][k + 1] = F[j][k], f[j][k] + (f[j][k] >> 31 & mod)), f[j][k] = 0; for (jl = 1; jl <= n; ++jl) for (kl = 1; kl <= jl; ++kl) { l = std::min(c[i], kl); f[jl][0] = (f[jl][0] + (ll)finv[kl] * (F[jl][kl] - F[jl - l][kl - l])) % mod; if (jl != n) { l = std::min(c[i] - 1, kl), f[jl][kl] = ((ll)f[jl][kl] - F[jl][kl] + F[jl - l][kl - l]) % mod; } } ans += f[n][0]; } ans = ans % mod * fact[n] % mod; printf("%lld\n", ans + (ans >> 63 & mod)); return 0; } - 第 个字符使用若干 (正整数) 个,然后将这一段切掉 (也是最正常的情况)。
- 1
信息
- ID
- 4394
- 时间
- 5000ms
- 内存
- 250MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者