(第十一届蓝桥杯省赛)试题H:子串分值和(思维)(样例不对)

题目链接:精选项目课程_IT热门课程_蓝桥云课课程 - 蓝桥云课

分析:这道题目我觉得还是挺好的一道题,所以过来记录一下:

首先看了n的数据范围我们就知道这道题目只能用o(n)或者o(nlogn)的复杂度通过,当然,o(n^2)也可以得部分分,下面我来给出正解:

我们不可能遍历S中的每个子串去求他的f值是多少,但是我们可以求取S中每个字符对最终答案的贡献最后把每个字母的贡献求和即可,以ababc为例,我们先来分析第三个字符a的贡献,我们先假设这个a对于每一个包含他的子串都有贡献1,那么他的贡献就是3*3=9,因为他是S序列左边第三个也是右边第三个,我们在S中前三个位置任选一个数作为l,再从S中后三个位置中任选一个数作为r,那么任意的[l,r]序列中都包含这个a,但是我们很快发现这样思考问题是不对的,因为这样会重复计算,举个例子,aba也是S的子串,但是这个子串中有两个a,贡献是属于哪个a的呢?所以显然不能像刚才那样进行计算,那是不是我们当前字母的贡献的区间的l包含于当前字母上一次出现的位置的右边第一个位置到当前字母的位置的区间,而当前字母的贡献的区间的r包含在当前字母下一次出现的位置的左边第一个位置到当前字母的位置的区间,这样的规定就保证了我们所选取的每个子串都是每个字母只能含有一次,但是我们这样会漏掉一些情况,不难发现对于aba来说,我们的字母a还是有贡献的,但是我们像刚才那样定义永远无法遍历到含有相同字母的子串,所以那样定义贡献会导致漏解,为了不重不漏地考虑到每个子串,我们可以这样定义一个字母的贡献:当前字母的贡献的区间的l包含在当前字母上次出现的位置的右一个位置到当前字母的区间,而当前字母的贡献的区间的r包含在当前字母的位置到S字符串的右端点的区间,这样保证了我们每次所选取的子串中出现重复字母时该字符串的贡献都算作第一个出现的字母的贡献,这样就可以不重不漏地计算上每一个字母的贡献。特殊情况就是某个字母左边没有与其相同的字母,那么我们就按照左边界处理即可。

下面是代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=30;
vector<int>p[N];
int main()
{
	string s;
	cin>>s;
	long long len=s.size();
	s=" "+s;//让下标从1开始 
	for(int i=1;i<=len;i++)
		p[s[i]-'a'+1].push_back(i);
	long long ans=0;
	for(int i=1;i<=len;i++)
	{
		int t=s[i]-'a'+1;
		int last;
		int id=lower_bound(p[t].begin(),p[t].end(),i)-p[t].begin();//求出第i个字母是第几次出现 
		if(id) last=p[t][id-1]+1;
		else last=1;
		ans+=(i-last+1)*(len-last+1);
	}
	cout<<ans;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/AC__dream/article/details/123972726
今日推荐