【2020蓝桥杯省赛】【字符串】子串分值(详解!)(两种解法)

目录

题目

题目链接

输入描述

输出描述

数据范围

测试样例

输入样例

输出样例

样例说明

提交结果截图

详细分析

坑点

解法1:半暴力法(复杂度为)(超过时间限制)

源代码

解法2:计算贡献值法(复杂度为(AC) 

源代码


题目

对于一个字符串 S,我们定义 S 的分值 f(S)为 S中恰好出现一次的字符个数。

例如 f(“aba”)=1,f(“abc”)=3, f(“aaa”)=0。

现在给定一个字符串 S[0…n−1](长度为 n),请你计算对于所有 S 的非空子串 S[i…j](0≤i≤j<n), f(S[i…j]) 的和是多少。

题目链接

子串分值 - 蓝桥云课 (lanqiao.cn)https://www.lanqiao.cn/problems/499/learning/

输入描述

输入一行包含一个由小写字母组成的字符串 S。

输出描述

输出一个整数表示答案。

数据范围

对于 20% 的评测用例,1≤n≤10;
对于 40% 的评测用例,1≤n≤100;
对于 50% 的评测用例,1≤n≤1000;
对于 60% 的评测用例,1≤n≤10000;
对于所有评测用例,1≤n≤100000。

测试样例

输入样例

ababc

输出样例

21

样例说明

所有子串 f 值如下:

a     1
ab    2
aba   1
abab  0
ababc 1
 b    1
 ba   2
 bab  1
 babc 2
  a   1
  ab  2
  abc 3
   b  1
   bc 2
    c 1

提交结果截图

详细分析

坑点

  1. 结果用int型是存不下的,最好用long long型来存储结果(提交后答案错误,我开始以为我算法错了,后面才发现是因为结果超过了int型的存储范围,结果变成了负数了)
  2. 这题只有复杂度为\color{Red} O\left ( n \right )才能通过所有测试点(开始写的那个,也就是解法1,效率还是低了些)

解法1:半暴力法(复杂度为\color{DarkRed} \theta \left ( \frac{n\left ( n+1 \right )}{2} \right ) = \theta\left ( n^{2} \right ))(超过时间限制)

  1. 【定义变量】用一个字母表存储每个字符出现的次数,用num存储以i(0<= i < 字符串长度)开始的子串的f值,用sum存储子串f值的和。(字母表和num每次 i 改变后要重置!
  2. 【双重循环】子串的起点为 i(0<= i < 字符串长度),固定起点,终点从起点开始向后移动,每次移动会有一个新的字符增加,我们给相应的字母表中该字符对应的元素elem+1,然后我们就可以根据该元素的值判断当前子串的f值sum,若elem = 1,说明新增一个恰好只出现一次的字符,则num++;若elem = 2,说明减少一个恰好只出现一次的字符,则num--。然后sum+=num即可。
  3. 【得出结果】循环结束后,sum值其实就出来了,输出即可。

源代码

#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 alphabet[27], num, sum =0;
	for (long long i = 0; i < len; i++)//子串起点
	{
		num = 0;
		memset(alphabet, 0, sizeof(alphabet));
		for (long long j = i; j < len; j++)
		{
			int k = str[j] - 'a';
			alphabet[k]++;
			if (alphabet[k] == 1)//新增一个恰好只出现一次的字符
				num++;
			else if (alphabet[k] == 2)//减少一个恰好只出现一次的字符
				num--;
			sum += num;
		}
	}
	cout << sum;
	return 0;
}

解法2:计算贡献值法(复杂度为\color{Red} O\left ( 26*2n \right ) = O\left ( n \right ))(AC) 

 这个算法是计算字符串中每个字符的贡献值即影响因子,将贡献值累加就得到了结果。

那么该如何来计算每个字符的贡献值呢?

所谓贡献值,即该字符能够影响到的子串个数,就比如

012345
bacbeb

表格中的3号b能够影响多少个子串呢?

列举一下有:acbe,acb,cbe,cb,b,be共有6个(这些子串的f值都因为b增加了1,而bacb这个子串不会因为3号b增加f值).

那么如何用数学的方法计算出来贡献值呢?

这些子串的起点可以为1、2、3,终点可以为3、4,即(3-0)*(5-3) = 6

其中0是3号b往左遇到的第一个b的标号,而5是3号b往右遇到的第一个b的标号。

推论:一个字符(位置为i)的影响范围由其往左边遍历遇到的第一个相同字符(位置为left)和其往右边遍历遇到的第一个相同字符(位置为right)所决定。其贡献值为(i - left)*(right - i)

本题中该如何使用这个推论呢?

本题中,采用这个方法计算每个字符的贡献值,然后累加即为所求的f值之和。

源代码

#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++)//向右寻找相同的
			if (str[right] == s)
				break;

		for (left = i - 1; left >= 0; left--)//向左寻找相同的
			if (str[left] == s)
				break;
		sum += (right - i) * (i - left);//累加贡献度
	}
	cout << sum << endl;
	return 0;
}

  • 49
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 25
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值