1 条题解
-
0
「SDOI / SXOI2022」子串统计 题解
问题分析
核心问题
给定字符串,考虑所有从开始,每次删除开头或结尾字符,直到得到单个字符的操作序列。每个序列的贡献是路径上所有中间串在中出现次数的乘积。求所有个操作序列的贡献之和。
关键观察
观察1:问题结构
每个操作序列对应从完整区间到某个长度为1的区间的一条路径,路径上的每个节点是一个子串。
观察2:贡献计算
对于区间,其贡献因子为,即该子串在中的出现次数。
观察3:路径计数
从可以转移到或,因此路径数呈指数级增长。
算法思路
方法一:区间动态规划
状态设计: 设表示从子串开始的所有操作序列的贡献和。
状态转移:
$$dp[l][r] = \text{occ}(S[l:r]) \times (dp[l+1][r] + dp[l][r-1]) $$边界条件: 当时,(单个字符,没有后续操作)
答案:
方法二:基于后缀自动机的优化
挑战:直接区间DP的复杂度为,对于不可行。
优化思路:
- 使用后缀自动机预处理所有子串的出现次数
- 利用字符串性质优化状态转移
算法细节
后缀自动机预处理
步骤:
- 构建的后缀自动机
- 计算每个状态的集合大小
- 对于任意子串,可以快速查询其出现次数
复杂度:
动态规划优化
优化1:状态压缩
观察发现,很多不同的对应相同的子串,可以按子串而不是区间来设计状态。
优化2:利用回文性质
如果字符串具有特殊结构(如随机字符串、重复字符串),可以设计更高效的算法。
优化3:分治策略
将字符串分成若干段,分别计算后再合并。
方法三:生成函数方法
核心思想:将问题转化为在子串DAG上的路径计数问题。
步骤:
- 构建所有本质不同子串的DAG
- 在DAG上进行动态规划
- 利用后缀自动机优化状态空间
复杂度分析
直接区间DP
- 时间复杂度:
- 空间复杂度:
- 适用于(测试点1-5)
优化后的算法
目标复杂度:或
实现要点
后缀自动机构建
// 构建后缀自动机,计算每个状态的出现次数 struct State { int len, link; map<char, int> next; int cnt; // 出现次数 };状态转移优化
对于大规模数据,需要:
- 只存储必要的DP状态
- 使用记忆化搜索避免重复计算
- 利用字符串的局部性质
特殊性质利用
测试点6-8:随机字符串
- 字符从{a,b}中等概率随机生成
- 可以利用随机性设计启发式算法
- 期望复杂度可能更优
小规模数据(测试点1-5)
可以直接使用的区间DP。
中等规模数据(测试点9-14)
需要结合后缀自动机和优化的DP。
数学洞察
组合意义
答案可以看作是所有可能"收缩路径"的权重和,其中每条路径的权重是路径上所有节点权重的乘积。
生成函数视角
设表示字符串的答案,则有:
$$F(S) = \text{occ}(S) \times [F(S[1:]) + F(S[:-1])] $$这是一个递归结构。
优化策略
策略1:状态去重
如果,那么。
策略2:早期剪枝
当时,该状态对答案没有贡献。
策略3:记忆化搜索
使用哈希表存储已计算的状态。
总结
本题的核心在于将指数级的操作序列计数问题转化为多项式复杂度的动态规划问题,关键技巧包括:
- 后缀自动机:高效计算子串出现次数
- 区间DP:刻画所有可能的删除路径
- 状态优化:利用字符串性质减少状态数
- 组合计数:处理乘积形式的贡献计算
对于的数据规模,需要设计或的算法,这通常需要结合后缀自动机和精心优化的动态规划。
- 1
信息
- ID
- 3657
- 时间
- 1500ms
- 内存
- 256MiB
- 难度
- 9.8
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者