贡献度思想
题目大意:求给定字符串所有字串的F(S)之和,规定F(S)表示为字符串S中恰好出现一次的字符的个数
首先,我们先考虑暴力枚举的做法
先枚举区间的左端点,然后设置一个num数组,用来记录某个字符出现的次数。和sum,用来标记仅仅出现一次的字符的个数。(这里也可以使用set来判断某个字符有没有出现过,但不要使用set,而是unordered_set,因为我们不需要用到set的排序功能,只需要用到set的去重功能,而set的排序是需要O(logN)的时间的)
但这样做肯定会超时的,我们考虑能不能进行优化
根据题目范围,我们需要用一个O(NlogN),或者O(N)的算法
在上面的暴力算法中,我们枚举的是区间,然后在区间中找到仅出现一次的字符,而枚举区间是需要两重for循环的。那么,我们是不是可以直接枚举所有字符,然后找到这个字符仅仅出现一次的区间呢?
答案是可以的!我们只需要找到该字符Pos左边第一次出现的位置Left,和右边第一次出现的位置Right,那么该字符仅出现一次的区间个数为SUM=(Right - Pos)* (Pos - Left)
我们把这个SUM称为该字符对整个字符串的贡献度
说明一下上面的公式是怎么来的。
- 首先,我们举个例子:S=bacbeb,对于第二个b字符
Pos=2,Left=0(字符串S下表从0开始),Right=5,我们可以得到SUM=(5-2)*(2-0)=6
对应的区间就是:acbe,acb,cbe,cb,b,be- 观察上面的六个区间,我们可以发现任意区间都是由三部分组成的:LEFT + b + RIGHT
- 其中LEFT区间我们可以选择字符ac或c,RIGHT区间我们可以选择字符e,同时,左侧我们也可以什么都不选,右侧我们也可以什么都不选(什么都不选的情况容易忽略)。
- 由于不管是左侧还是右侧的区间,我们都必须选择与b相邻的,例如左侧我们不能只选择a而不选择c。所以说我们左侧区间的选择就是b往左走最多能走的距离(Pos - Left + 1),右侧区间的选择就是b最多能往右走的距离(Right - pos + 1),由于我们也可以不走,所以上面的区间还要 +1
- 这样,问题就简化成了左边有n种选择,右边有m种选择,所有选择的个数就是n*m(乘法定理)
#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++)//向右寻找相同的,找不到right=len
if (str[right] == s)
break;
for (left = i - 1; left >= 0; left--)//向左寻找相同的,找不到left=-1
if (str[left] == s)
break;
sum += (right - i) * (i - left);//累加贡献度
}
cout << sum << endl;
return 0;
}
思路来源:【字符串】子串分值(详解!)(两种解法)
和上一题几乎完全相同,不同点在于本题的F(S)表示出现过的字符个数
例如,F("aaa"),上一题为0,本题为1
分析,很恶心,写了很久的分析,结构摁了一下CTRL+Z没了,我原本只是像撤回代码,结果把我的分析也撤回了,真的是恶心,不是第一次了!CSDN的撤回一定要慎用!!!
那么这里就简单说一下吧!
在上一题我们已经得到了贡献度的概念,以及左右区间的概念。
在本题中,对于两个相同的字符,由于第一次出现的字符会先提供贡献,那么从此往后所有与它相同的字符都不再提供贡献了!
所以说,对于任意字符,他的左区间就是它左侧第一次出现相同字符位置的右边一位。
右区间就是整个字符串的右端点,因为当前字符已经提供了该字符的贡献度,那么从此往后所有与它相同的字符都毫无意义了!不需要在考虑!它将为它后面的每一个区间提供贡献度!由它自己提供!
我们可以发现,它与上一题的不同就在于右区间恒为字符串右端点,那么只要稍微修改一下上一题的代码就可以了。
#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 = len;
long long sum = 0;
char s;
for (long long i = 0; i < len; i++)
{
s = str[i];
for (left = i - 1; left >= 0; left--)//向左寻找相同的,找不到left=-1
if (str[left] == s)
break;
sum += (right - i) * (i - left);//累加贡献度
}
cout << sum << endl;
return 0;
}
思路来源:子串分值和