1 条题解
-
0
题解
题目大意
给定初始字符串 (长度 )和参数 ,按以下规则不断在末尾添加字符:
- 设当前字符串为 ,长度为 ,取其后 个字符作为子串 。
- 找出 在 中所有出现的位置(包括中间出现的位置,不包含最后一个位置)。
- 统计这些出现位置后面一个字符的频率。
- 取频率最高的字符作为要添加的字符 (频率相同取字典序最小),若 没有在其他位置出现过,则 。
- 将 添加到 末尾。
现给定 (,),要求输出长度分别为 的完整字符串(每行一个)。
思路分析
直接模拟到 是不可能的,因为 可达 。
关键在于观察到:整个过程是一个确定性的状态转移,状态是当前的后 个字符(长度为 的字符串)。
核心观察
- 状态集的大小有限:最多 种(虽然 最大 ,但实际转移过程中不同状态不会无限多,因为转移规则固定,最终会进入循环)。
- 因此,从初始状态出发,经过若干步后一定会进入一个循环节。
- 我们的任务是:只生成从位置 到 的字符,而不必生成全部 个字符。
- 需要高效地查询“某个状态 在历史中出现的所有位置的后一个字符频率”。
高效维护“历史出现信息”
设 是一个长度为 的字符串。
我们需要知道 在 的所有出现位置(不包含最后一次作为后缀的位置)的后一个字符的频次统计。我们可以动态维护:
用一个哈希表cnt[hash(R)][ch]记录:每当 出现一次(不是最后一次)时,其后一个字符 的累计出现次数。如何更新:
- 初始时,扫描 ,对所有长度为 的子串 ,如果它后面有字符 ,则
cnt[hash(R)][ch]++。 - 生成过程中,假设当前状态是 ,我们根据
cnt[hash(R)]决定下一个字符 。 - 添加 后,新的末尾 子串是 。
同时,我们刚刚让 多出现了一次(即作为倒数第二个长度为 的子串,后面跟了 ),所以cnt[hash(R)][c]++。 - 更新当前状态为 。
这样,查询下一个字符只需 。
状态表示与滚动哈希
由于 可能很大,不能直接存字符串,用滚动哈希(双模数哈希防止冲突)表示当前后 个字符的哈希值。
转移时:- 已知 的哈希值 。
- 去掉第一个字符 ,加上新字符 。
- 新哈希值: $ h1' = \big((h1 - first \cdot p1^{k-1}) \cdot BASE1 + c\big) \bmod MOD1 $ 其中 是预先算好的幂。
处理超大 :周期检测
我们只需要生成 到 的字符,但 可能非常大( 级别),必须避免模拟到 。
注意到状态转移形成一个有向图(每个状态指向下一个状态),由于状态数有限,路径必然进入循环。
我们可以模拟直到发现某个状态第二次出现,记录:start_pos[state]:该状态第一次出现时的字符串长度。char_at[pos]:字符串在位置 ()的字符。
发现循环后,设循环节长度为 ,那么对于任意位置 ,我们可以 知道它的字符: $ \text{字符} = \text{char\_at}[ \text{start} + ((p - \text{start}) \bmod cycle) ] $ 其中 是循环开始位置。
输出部分
因为 ,我们可以:
- 模拟直到长度 ,并记录每个位置的字符(或只记录 到 的部分)。
- 如果 在循环内,用周期公式直接得到 到 的字符。
- 最后输出时,每个长度的字符串 = 初始 + 生成的部分。
时间复杂度
- 初始统计 中所有长度 子串的频率:。
- 模拟至多 步,状态数有限,通常远小于 。
- 哈希表操作 ,每次查询下一个字符 。
- 总复杂度大致 ,可接受。
关键点总结
- 状态压缩:用滚动哈希表示长度 的字符串。
- 频率动态更新:
cnt表记录每个状态的历史后继字符频率,避免每次扫描历史。 - 周期检测:快速跳过巨大的 到 之间的大部分步数。
- 按需输出:只生成 到 的字符,节省时间和空间。
- 1
信息
- ID
- 5808
- 时间
- 5000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者