2262. 字符串的总引力
考点
- 线性DP
- 状态转移方程的设计
思路
一、问题描述
给定一个长度为 \(n\) 的字符串 \(s\),定义任意子串的 appeal 为该子串中 不同字符的个数(distinct characters)。
目标是计算: \[ \sum_{\text{所有子串 } s[l..r]} \text{appeal}(s[l..r]) \] 其中 \(0 \le l \le r < n\),且 \(n \le 10^5\)。
二、朴素思路及其瓶颈
对于每个右端点 \(r\),枚举所有左端点 \(l\),并重新统计子串 \(s[l..r]\) 的 distinct 字符数:
- 子串总数为 \(O(n^2)\)
- 每个子串若再统计字符,复杂度更高
因此朴素做法不可行。
关键问题在于: 是否真的需要对每个子串“重新统计”?
三、核心建模思想:从“子串”转为“贡献”
3.1 固定右端点的视角
将所有子串按右端点分类。
设:
右端点为 \(i\)(使用 1-based 下标)
所有以 \(i\) 结尾的子串为: \[ s[1..i],\ s[2..i],\ \dots,\ s[i..i] \] 共 \(i\) 个
定义: \[ f(i) = \sum_{l=1}^{i} \text{appeal}(s[l..i]) \] 那么最终答案就是: \[ \sum_{i=1}^{n} f(i) \]
3.2 关键问题转化
从 \(i-1\) 推到 \(i\) 时,只新增了一个字符 \(s[i]\)。
问题转化为:
当字符 \(s[i]\) 加入后, 在所有以 \(i\) 结尾的子串中,有多少个子串的 appeal 会增加 1?
四、单字符贡献的精确刻画
设当前字符为 \(c = s[i]\)。
定义:
- \(\text{last}[c]\):字符 \(c\) 上一次出现的位置(1-based)
- 若从未出现过,则 \(\text{last}[c] = 0\)
4.1 哪些子串会因为 \(c\) 而 appeal +1?
考虑所有以 \(i\) 结尾的子串 \(s[l..i]\):
- 若 \(l \le \text{last}[c]\): 子串中已经包含 \(c\),appeal 不变
- 若 \(l > \text{last}[c]\): 子串中第一次出现 \(c\),appeal +1
因此,新增贡献的子串数量为: \[ i - \text{last}[c] \]
4.2 状态转移方程(核心)
由此得到极其简洁的状态转移: \[ \boxed{ f(i) = f(i-1) + \bigl(i - \text{last}[s[i]]\bigr) } \] 并在计算完后更新: \[ \text{last}[s[i]] \leftarrow i \]
五、整体算法流程
- 使用数组
last[26]记录每个字符上一次出现的位置(初始化为 0) - 用变量
f表示当前的 \(f(i)\) - 用变量
ans累加所有 \(f(i)\) - 单次从左到右扫描字符串
六、复杂度分析
时间复杂度: \[ O(n) \]
空间复杂度: \[ O(1) \quad (\text{字符集大小为常数}) \]
七、AC代码
1 | class Solution { |