蓝桥杯 试题H:子串分值
题目内容如下:
首先,理解题意——求解字符串所有子串Si的f(Si)之和,其中f(Si)实际就是统计字符串Si中只出现一次的字母的数目。
再根据样例说明,很容易想到,两层循环去遍历字符串的所有子串(子串按以不同字符开头,依次递增到最后一个字符),对于每一个子串,遍历的同时记录其f(Si)。
前面已经说到,f(Si)实际就是统计字符串Si中只出现一次的字母的个数,那么利用一个数组cnt[26]来统计字符串中每个字母出现的次数,用ff来记录Si中只出现一次的字母的个数。前面已经说到,每一层外层循环对应的所有的子串,规律是首字母相同,然后长度依次递增,那么cnt数组和ff值的记录,可以在前一次的基础上更改,不必对于每一个子串再单独写一个循环,去统计求ff。代码如下:
#include<bits/stdc++.h>
using namespace std;
string s;
int Function() {
int ff; //记录每一个子串的分值
int sum=0; //记录总的分值
int n = s.size();
int i, j;
//int cnt[26] = { 0 };//统计a-z出现的次数
//两次循环遍历所有的子串
for (i = 0; i < n; i++) {
//以不同字符开始递增子串,每轮循环需要将统计结果清零
//memset(cnt, 0, sizeof(cnt));
int cnt[26] = { 0 };//统计a-z出现的次数
ff = 0;
for (j = i; j < n; j++) {
//如果第一次出现 ff+1
if (cnt[s[j] - 'a'] == 0)
//记录每个子串的分值
ff++;
//第二次出现 需要将此字母第一次出现的分值减去 ff-1
else if(cnt[s[j]-'a']==1)
ff--;
//第三次及以上出现 对ff不再有影响
cnt[s[j] - 'a']++;
sum += ff;
}
}
return sum;
}
int main() {
cin >> s;
cout << Function() << endl;
return 0;
}
暴力求解只能通过60%的示例🤡
看见网上很多巧妙的求解,是说循环遍历字符串每一个字母,统计每一个字母对子串分值的贡献,然后求和——sum+=(i-j)*(j-k),其中S[i]=S[j]=S[k],且i与j之间以及j与k之间不存在S[p]=S[j]。但是我不明白原理是什么,于是将就上面的样例ababc,做了如下分析:
子串 | f(Si) | 做出贡献的字符 |
---|---|---|
a | 1 | a1(区分两个a) |
ab | 2 | a1 b1 |
aba | 1 | b1 |
abab | 0 | |
ababc | 1 | c |
b | 1 | b1 |
ba | 2 | b1 a2 |
bab | 1 | a2 |
babc | 2 | a2 c |
a | 1 | a2 |
ab | 2 | a2 b2 |
abc | 3 | a2 b2 c |
b | 1 | b2 |
bc | 2 | b2 c |
c | 1 | c |
针对第二个字母a,即a2,可以看出,它在子串ba,bab,babc,a,ab,abc中做出了贡献。再分析上面六个子串可以发现,它们是在第一个字母a(即a1)到字符串末尾(因为a2后没有字母a了)之间的字符串包含a2的子串,即babc中包含a的子串,分析它们的规律为:以a为中心,往后走一轮——a,ab,abc,再考虑在前面增加字符——ba,bab,babc,前面增加的字符即a1-a2之间的字符(此题中两个a之间只有b)。当然,也可以理解为以a为中心,先往前面走一轮,再在后面增加,过程就变成了a,ba–>ab,bab–>abc,babc。因此得到上述规律——每一个字母的贡献值为(i-j)*(j-k),i、j、k的含义前面有解释。 | ||
代码实现中需要注意的是,如何标注当前下标为j的字母的前驱字母下标i和后驱字母下标k——用Pre[]和Next[]存储,其中Pre[i]即为当前下标为i的字母的前驱字母下标,Next[i]即为当前下标为i的字母的后驱字母下标。还需要注意的是,初始时,全部的Pre[i]为-1,Next[i]为n(字符串长度)——初始时假设每一个字母只出现一次,那么前驱就为-1,后驱就为n。代码如下: |
#include<bits/stdc++.h>
using namespace std;
string s;
int Function() {
int n = s.size();
int *Pre = new int[n];
int *Next = new int[n];
int where[26]; //统计每个字母最后一次出现的下标
int i;
for (i = 0; i < 26; i++) {
where[i] = -1;
}
for (i = 0; i < n; i++) {
Pre[i] = where[s[i] - 'a'];
where[s[i] - 'a'] = i;
}
for (i = 0; i < 26; i++) {
where[i] = n;
}
for (i = n - 1; i >= 0; i--) {
Next[i] = where[s[i] - 'a'];
where[s[i] - 'a'] = i;
}
int sum = 0; //统计
for (i = 0; i < n; i++) {
sum += (i - Pre[i])*(Next[i] - i);
}
return sum;
}
int main() {
cin >> s;
cout << Function() << endl;
return 0;
}
最后全部通过了!
这里参考了很多其他关于这道题目的博客,一一列出链接有点麻烦~ 我只是想记录我的学习过程,如果有相关博主觉得此贴不妥~ 我改!