第十一届蓝桥杯软件类省赛第二场——H:子串分值
题目描述
对于一个字符串 SSS,我们定义 S 的分值 f(S) 为 S 中恰好出现一次的字符个数。例如 f("aba")=1,f("abc")=3, f("aaa")=0
现在给定一个字符串 S0⋯n−1(长度为 n,1≤n≤10^5),请你计算对于所有 S 的非空子串 Si⋯j(0≤i≤j<n), 求f(Si⋯j)的和是多少。
输入描述
输入一行包含一个由小写字母组成的字符串 S。
输出描述
输出一个整数表示答案。
输入输出样例
示例
输入
ababc
输出
21
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
思路:这道题不能单纯的用暴力枚举将所有的子串都枚举出来,这样的时间复杂度已经达到O(n^2),就更别谈后面的操作l。后面去网上看了一下别人的思路
不要正向考虑问题,我们可以通过每次当前看到的字符,就分析该字符,从上一次出现到下一次出现之间这个距离,即将原问题转化为求整个字符串中
每个字符对整体的贡献值。
怎么求一个字母对这一列中只包含这个位置字母的贡献值呢?
公式:该字母的贡献值 = (该字母当前出现的位置 - 该字母上一次出现的位置) * (该字母下一次出现的位置 - 该字母当前出现的位置)
总之,这道题不能从常规角度来解决问题就对了!!!
温馨提示:建议初学算法的朋友们,如果用一种正常思路解决不出来,就要跳出该题,从另一个角度切人(通常这个角度都是一般人想不到,需要大量的刷题和对问题的分析能力等诸多因素)
下面给出Java的AC代码:
该代码是参考网友的C++代码改写的,自己真的很难想出来。
1 public class SubStrGoal { 2 private static int N = 100000; 3 4 // 存储第i个位置字符上一次出现该字符的位置 5 private static int[] pre = new int[N + 10]; 6 7 // 存储第i个位置字符下一次出现该字符的位置 8 private static int[] late = new int[N + 10]; 9 10 // 用于实时更新每个字母最近一次出现的位置,给字母编号:a - 0 、b - 1、 c - 2 ...以此类推 11 private static int[] cur = new int[26]; 12 public static void main(String[] args) { 13 Scanner input = new Scanner(System.in); 14 String data = input.next(); 15 f(data.toCharArray()); 16 } 17 18 private static void f(char[] data) { 19 int n = data.length; 20 21 // 初始化cur数组 22 for (int i = 0; i < 26; i++) { 23 cur[i] = -1; 24 } 25 26 // 处理pre数组 27 for (int i = 0; i < n; i++) { 28 int index = data[i] - 'a'; 29 // 更新pre值 30 pre[i] = cur[index]; 31 cur[index] = i; 32 } 33 34 // 重新重置一下cur数组 35 for (int i = 0; i < 26; i++) { 36 cur[i] = n; 37 } 38 39 // 处理late数组,这里需要从后往前遍历,统计起来比较方便 40 for (int i = n - 1; i >= 0; i--) { 41 int index = data[i] - 'a'; 42 // 更新late值 43 late[i] = cur[index]; 44 cur[index] = i; 45 } 46 47 long ans = 0; 48 for (int i = 0; i < n; i++) { 49 ans += (long) (i - pre[i]) * (late[i] - i); 50 } 51 System.out.println(ans); 52 } 53 }