贡献度思想

例题一:字串分值

题目大意:求给定字符串所有字串的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;
}

思路来源:子串分值和

posted @ 2022-05-05 08:41  光風霽月  阅读(49)  评论(0编辑  收藏  举报