【算法题】蓝桥杯 试题H:子串分值

蓝桥杯 试题H:子串分值

题目内容如下:
在这里插入图片描述
在这里插入图片描述

首先,理解题意——求解字符串所有子串Si的f(Si)之和,其中f(Si)实际就是统计字符串Si中只出现一次的字母的数目。
再根据样例说明,很容易想到,两层循环去遍历字符串的所有子串(子串按以不同字符开头,依次递增到最后一个字符),对于每一个子串,遍历的同时记录其f(Si)。
前面已经说到,f(Si)实际就是统计字符串Si中只出现一次的字母的个数,那么利用一个数组cnt[26]来统计字符串中每个字母出现的次数,用ff来记录Si中只出现一次的字母的个数。前面已经说到,每一层外层循环对应的所有的子串,规律是首字母相同,然后长度依次递增,那么cnt数组和ff值的记录,可以在前一次的基础上更改,不必对于每一个子串再单独写一个循环,去统计求ff。代码如下:

#include<bits/stdc++.h>
using namespace std;

string s;
int Function() {
	int ff;    //记录每一个子串的分值
	int sum=0; //记录总的分值
	int n = s.size();
	int i, j;
	//int cnt[26] = { 0 };//统计a-z出现的次数

	//两次循环遍历所有的子串
	for (i = 0; i < n; i++) {
		//以不同字符开始递增子串,每轮循环需要将统计结果清零
		//memset(cnt, 0, sizeof(cnt));
		int cnt[26] = { 0 };//统计a-z出现的次数
		ff = 0;

		for (j = i; j < n; j++) {
			//如果第一次出现 ff+1
			if (cnt[s[j] - 'a'] == 0)
				//记录每个子串的分值
				ff++;

			//第二次出现 需要将此字母第一次出现的分值减去 ff-1
			else if(cnt[s[j]-'a']==1)
				ff--;
			//第三次及以上出现 对ff不再有影响
			cnt[s[j] - 'a']++;
			sum += ff;
		}
	}
	return sum;
}
int main() {
	cin >> s;
	cout << Function() << endl;
	return 0;
}

暴力求解只能通过60%的示例🤡
在这里插入图片描述
看见网上很多巧妙的求解,是说循环遍历字符串每一个字母,统计每一个字母对子串分值的贡献,然后求和——sum+=(i-j)*(j-k),其中S[i]=S[j]=S[k],且i与j之间以及j与k之间不存在S[p]=S[j]。但是我不明白原理是什么,于是将就上面的样例ababc,做了如下分析:

子串f(Si)做出贡献的字符
a1a1(区分两个a)
ab2a1 b1
aba1b1
abab0
ababc1c
b1b1
ba2b1 a2
bab1a2
babc2a2 c
a1a2
ab2a2 b2
abc3a2 b2 c
b1b2
bc2b2 c
c1c
针对第二个字母a,即a2,可以看出,它在子串ba,bab,babc,a,ab,abc中做出了贡献。再分析上面六个子串可以发现,它们是在第一个字母a(即a1)到字符串末尾(因为a2后没有字母a了)之间的字符串包含a2的子串,即babc中包含a的子串,分析它们的规律为:以a为中心,往后走一轮——a,ab,abc,再考虑在前面增加字符——ba,bab,babc,前面增加的字符即a1-a2之间的字符(此题中两个a之间只有b)。当然,也可以理解为以a为中心,先往前面走一轮,再在后面增加,过程就变成了a,ba–>ab,bab–>abc,babc。因此得到上述规律——每一个字母的贡献值为(i-j)*(j-k),i、j、k的含义前面有解释。
代码实现中需要注意的是,如何标注当前下标为j的字母的前驱字母下标i和后驱字母下标k——用Pre[]和Next[]存储,其中Pre[i]即为当前下标为i的字母的前驱字母下标,Next[i]即为当前下标为i的字母的后驱字母下标。还需要注意的是,初始时,全部的Pre[i]为-1,Next[i]为n(字符串长度)——初始时假设每一个字母只出现一次,那么前驱就为-1,后驱就为n。代码如下:
#include<bits/stdc++.h>
using namespace std;

string s;
int Function() {
	int n = s.size();
	int *Pre = new int[n];
	int *Next = new int[n];
	int where[26];  //统计每个字母最后一次出现的下标
	int i;
	for (i = 0; i < 26; i++) {
		where[i] = -1;
	}
	
	for (i = 0; i < n; i++) {
		Pre[i] = where[s[i] - 'a'];
		where[s[i] - 'a'] = i;
	}

	for (i = 0; i < 26; i++) {
		where[i] = n;
	}
	for (i = n - 1; i >= 0; i--) {
		Next[i] = where[s[i] - 'a'];
		where[s[i] - 'a'] = i;
	}

	int sum = 0; //统计
	for (i = 0; i < n; i++) {
		sum += (i - Pre[i])*(Next[i] - i);
	}

	return sum;
}
int main() {
	cin >> s;
	cout << Function() << endl;
	return 0;
}

最后全部通过了!
在这里插入图片描述
这里参考了很多其他关于这道题目的博客,一一列出链接有点麻烦~ 我只是想记录我的学习过程,如果有相关博主觉得此贴不妥~ 我改!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值