目录
题目
对于一个字符串 S,我们定义 S 的分值 f(S)为 S中恰好出现一次的字符个数。
例如 f(“aba”)=1,f(“abc”)=3, f(“aaa”)=0。
现在给定一个字符串 S[0…n−1](长度为 n),请你计算对于所有 S 的非空子串 S[i…j](0≤i≤j<n), f(S[i…j]) 的和是多少。
题目链接
子串分值 - 蓝桥云课 (lanqiao.cn)https://www.lanqiao.cn/problems/499/learning/
输入描述
输入一行包含一个由小写字母组成的字符串 S。
输出描述
输出一个整数表示答案。
数据范围
对于 20% 的评测用例,1≤n≤10;
对于 40% 的评测用例,1≤n≤100;
对于 50% 的评测用例,1≤n≤1000;
对于 60% 的评测用例,1≤n≤10000;
对于所有评测用例,1≤n≤100000。
测试样例
输入样例
ababc
输出样例
21
样例说明
所有子串 f 值如下:
a 1
ab 2
aba 1
abab 0
ababc 1
b 1
ba 2
bab 1
babc 2
a 1
ab 2
abc 3
b 1
bc 2
c 1
提交结果截图
详细分析
坑点
- 结果用int型是存不下的,最好用long long型来存储结果(提交后答案错误,我开始以为我算法错了,后面才发现是因为结果超过了int型的存储范围,结果变成了负数了)
- 这题只有复杂度为才能通过所有测试点(开始写的那个,也就是解法1,效率还是低了些)
解法1:半暴力法(复杂度为)(超过时间限制)
- 【定义变量】用一个字母表存储每个字符出现的次数,用num存储以i(0<= i < 字符串长度)开始的子串的f值,用sum存储子串f值的和。(字母表和num每次 i 改变后要重置!)
- 【双重循环】子串的起点为 i(0<= i < 字符串长度),固定起点,终点从起点开始向后移动,每次移动会有一个新的字符增加,我们给相应的字母表中该字符对应的元素elem+1,然后我们就可以根据该元素的值判断当前子串的f值sum,若elem = 1,说明新增一个恰好只出现一次的字符,则num++;若elem = 2,说明减少一个恰好只出现一次的字符,则num--。然后sum+=num即可。
- 【得出结果】循环结束后,sum值其实就出来了,输出即可。
源代码
#include <iostream>
#include <vector>
#include <string>
#include "string.h"
#include <algorithm>
using namespace std;
int main()
{
string str;
cin >> str;
long long len = str.length();
long long alphabet[27], num, sum =0;
for (long long i = 0; i < len; i++)//子串起点
{
num = 0;
memset(alphabet, 0, sizeof(alphabet));
for (long long j = i; j < len; j++)
{
int k = str[j] - 'a';
alphabet[k]++;
if (alphabet[k] == 1)//新增一个恰好只出现一次的字符
num++;
else if (alphabet[k] == 2)//减少一个恰好只出现一次的字符
num--;
sum += num;
}
}
cout << sum;
return 0;
}
解法2:计算贡献值法(复杂度为)(AC)
这个算法是计算字符串中每个字符的贡献值即影响因子,将贡献值累加就得到了结果。
那么该如何来计算每个字符的贡献值呢?
所谓贡献值,即该字符能够影响到的子串个数,就比如
0 | 1 | 2 | 3 | 4 | 5 |
b | a | c | b | e | b |
表格中的3号b能够影响多少个子串呢?
列举一下有:acbe,acb,cbe,cb,b,be共有6个(这些子串的f值都因为b增加了1,而bacb这个子串不会因为3号b增加f值).
那么如何用数学的方法计算出来贡献值呢?
这些子串的起点可以为1、2、3,终点可以为3、4,即(3-0)*(5-3) = 6
其中0是3号b往左遇到的第一个b的标号,而5是3号b往右遇到的第一个b的标号。
推论:一个字符(位置为i)的影响范围由其往左边遍历遇到的第一个相同字符(位置为left)和其往右边遍历遇到的第一个相同字符(位置为right)所决定。其贡献值为(i - left)*(right - i)
本题中该如何使用这个推论呢?
本题中,采用这个方法计算每个字符的贡献值,然后累加即为所求的f值之和。
源代码
#include <iostream>
#include <vector>
#include <string>
#include "string.h"
#include <algorithm>
using namespace std;
int main()
{
string str;
cin >> str;
long long len = str.length();
long long left, right;
long long sum = 0;
char s;
for (long long i = 0; i < len; i++)
{
s = str[i];
for (right = i + 1; right < len; right++)//向右寻找相同的
if (str[right] == s)
break;
for (left = i - 1; left >= 0; left--)//向左寻找相同的
if (str[left] == s)
break;
sum += (right - i) * (i - left);//累加贡献度
}
cout << sum << endl;
return 0;
}