本文已参与[新人创作礼]活动,一起开启掘金创作之路
Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目描述
这是3月19日代码源div2的每日一题。
知识点:哈希表
子串分值和 - 题目 - Daimayuan Online Judge
对于一个字符串 S ,我们定义 f(S) 为 S 中出现的不同的字符个数。 例如 f(aba)=2,f(abc)=3,f(aaa)=1。
现在给定一个字符串 S (假设长度为 len),请你计算 len−1∑i=0 len−1∑ j=i f(S[i:j]) 。
输入格式
输入一行包含一个由小写字母组成的字符串 SS 。
输出格式
输出一个整数表示答案。
样例输入
ababc
样例输出
28
数据规模
所有数据保证字符串长度 len≤1000000,字符串下标从 0 到 len−1。
问题分析
第一眼写法是枚举区间长度然后把每个区间的f[s]算出来并加一起,但这做法是n^2显然超时,所以我们就换个思路。
我们可以计算每个字符的贡献,并计算他们的贡献和。怎么计算贡献呢?因为区间内重复的字符只算一个,所以对于样例ababc中第一个a字母来说,它能被计数的情况就是从上一个字母a开始(因为没有所以实际上是从它开始),区间长度从1到len的len种情况(虽然到第3个位置也有个字符a,但我们只记录一个,所以记录的还是最先出现的a),那么第一个字母a所能提供的贡献一共就是len,即5。我们再看第二个字母a,和前面说的一样,他能被计数的情况,就是从上一个相同的字母开始,枚举区间长度直到到达字符串末尾,所以第二个字母所能提供的贡献就是:(当前字母位置 - 上一个相同字母)*(区间长度 - 当前字母位置)=2 *3=6;我们只要如法炮制,求得所有字符的贡献,并把计算贡献的总和即可。
虽然看似要找上一个相同字母的位置有点麻烦,但实际上我们用一个哈希表或者26长度的数组就可以,专门记录上一个相同字符出现的位置,因为一开始还没有相同的字符所以初始化为-1)
AC代码
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#define endl '\n';
typedef long long ll;
typedef pair<int, int>PII;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
string str;
cin >> str;
int a[26];
memset(a, -1, sizeof a);
ll res = 0;
int n = str.size();
for (int i = 0; i < n; i++)
{
res += (i - a[str[i]-'a']) * (n - i);
a[str[i] - 'a'] = i;
}
cout << res << endl;
return 0;
}