子串分值和 --第十一届蓝桥杯
问题描述
对于一个字符串 S,我们定义 S 的分值 f(S) 为 S 中出现的不同的字符个数。例如 f("aba"
)=2,f("abc"
)=3, f("aaa"
)=1。
现在给定一个字符串 S[0…n−1](长度为 n),请你计算对于所有 S 的非空子串 Si…j,f(S[i…j])的和是多少。
输入格式
输入一行包含一个由小写字母组成的字符串 S。
输出格式
输出一个整数表示答案。
样例输入
ababc
样例输出
28
样例说明
子串 f值
a 1
ab 2
aba 2
abab 2
ababc 3
b 1
ba 2
bab 2
babc 3
a 1
ab 2
abc 3
b 1
bc 2
c 1
评测用例规模与约定
对于 20% 的评测用例,1≤n≤10;
对于 40% 的评测用例,1≤n≤100;
对于 50% 的评测用例,1≤n≤1000;
对于 60% 的评测用例,1≤n≤10000;
对于所有评测用例,1≤n≤100000。
解题方法
1.暴力枚举法——时间复杂度(O3)
- 遍历所有子串
- 找出每个子串里面不同字符的个数
- 将所有子串不同字符的个数相加
这里用集合的方法找出每个字符串不同字符的个数,引入set库,申请一个集合,将字符串中的每个字符添加进去,最后返回集合的大小。
此方法时间复杂度很高,因此会超时。可拿40%的分数。
代码如下:
//
// Created by Charon on 2022/1/23.
//
#include "cstring"
#include "iostream"
#include "set"
using namespace std;
#define MAX 100000
//暴力枚举法 超时
int sum(int min, int max, char s[]) {
set<char> st; //申请一个集合st。
for (int i = min; i <= max; ++i) {
st.insert(s[i]); //在集合中插入元素
}
return st.size();
}
int main() {
char s[MAX];
cin >> s;
int n = 0;
for (int i = 0; i < strlen(s); ++i) {
for (int j = i; j < strlen(s); ++j) {
n += sum(i, j, s);
}
}
cout << n << endl;
return 0;
}
2.乘法原理——时间复杂度(O)
- 每个字母只有在第一次出现时才有贡献度,因此可以统计每个字母在第一次出现的情况下,能被多少子串所包含;
- 用 last[s[i]] 记录字母 s[i] 上一次出现的位置;
- 那么往左最多能延伸到 last[s[i]] + 1,其到第 i 个字母一共有 i - last[s[i]] 个字母;
同理往右最多能延伸到 n,其到第 i 个字母一共有 n - i 个字母; - 二者相乘,就是该字母被不同子串所包含的总次数;
代码如下:
//
// Created by Charon on 2022/1/23.
//
#include "cstring"
#include "iostream"
#include "set"
using namespace std;
#define MAX 100000
//乘法原理
int main() {
int last[256];
memset(last, -1, sizeof(last));
string s;
cin >> s;
int n = s.size();
long long counts = 0;
for (int i = 0; i < n; ++i) {
counts += (long long) (i - last[s[i]]) * (n - i);
last[s[i]] = i;
}
cout << counts << endl;
return 0;
}