【蓝桥杯C++】2020B H字串分值和 三种方法讲解

题目描述

对于一个字符串 S,我们定义 S 的分值 f(S) 为 S 中出现过一次的字符个数。例如 f(aba)=2,f(abc)=3,f(aaa)=1。

现在给定一个字符串 S0.....Sn-1​(长度为 n,1≤n≤100000),请你计算对于所有 S 的非空子串 Si⋯j​(0≤i≤j<n),f(Si⋯j​) 的和是多少。

输入描述

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

输出描述

输出一个整数表示答案。

输入输出样例

示例

输入

ababc

输出

21

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

举个例子:f(ababc)=28   

 a           1

ab          2

aba        2

abab      2

ababc    3

  b          1

  ba        2

  bab      2

  babc    3

    a        1

    ab      2

    abc    3

      b      1

      bc    2

        c    1

第一种方法 穷举法(暴力破解)

就是把每个字串的f值算出来相加,这样用了两个for循环,那么时间复杂度O(n^{2}),只能拿40.50分。

#include<bits/stdc++.h>
using namespace std;
int a[27];
int check(int x)
{
	if(a[x]==1)
		return 1;
	if(a[x]==0)
	{	
		a[x]=1;
		return 0;
	}
}
int fsum(char *str)
{
	int i,j,k;
	char c[100000];
	strcpy(c,str);
	
	
	
	
	
	int sum=0;
	int num;
	for(j=0;j<strlen(str);j++)
	{
		for(k=1;k<27;k++)
			a[k]=0;
		num=0;
		for(i=j;i<strlen(str);i++)
			{
				if(check(c[i]-96)==0)
				{
					num++; 
				} 
				sum+=num;
			}
	}
	return sum;
}

int main()
{
	char *str={"ababc"};
	cout<<fsum(str);
} 

 

第二种方法 动态规划

设置一个dp[i]数组来保存第i位字母贡献的值,来看下图: 

 由图我们可以清晰的总结出,第i位字母贡献的值总是等于第i-1位贡献的值+第i位字母的位置-第i位字母上一次出现的位置(如果未出现则为0),即dp数组表达式为:dp[i]=dp[i-1]+i-a[s[i]-'a']

用a[i]来代表26位字母出现的位置,其中i=0~25,分别对应了a~z。如a[1]=0,表示b之前在第0位出现过。

故只需一遍循环就可得出结果,那么时间复杂度为O(n)。

#include<bits/stdc++.h>
using namespace std;
int a[26]={0};
int main()
{
	char s[100000];
	long long sum,n,i;
	int dp[100000]; 
	scanf("%s",s);
	dp[0]=0;
	sum=0;
	n=strlen(s)+1;
	for(i=1;i<n;i++)
	{
		dp[i]=dp[i-1]+i-a[s[i-1]-'a'];
		a[s[i-1]-'a']=i;
		sum+=dp[i];
	}
	printf("%lld",sum);
} 

第三种方法    乘法原理

        不从每一个字串的f值相加这个方向考虑,而是从ababc中每个字母贡献的值来考虑,从而把加法转化为乘法。 

        

        还是以ababc为例,可以看到第一个a在所有子串种出现了5次,所以它贡献值为5;第二个b出现了8次,所以它的贡献值为8;重点为第三个a,它虽然在所有子串中出现了9次,但是有其中的三个字串中包含了第一个a,并且这第一个a的贡献值已经被计了,故第二个a的贡献值不是9,而是6。

        以此类推,我们得到f值的和为5+8+6+4+5=28。

        到这肯定有些同学会疑问,那后面重复出现的字母贡献值如何计算?

         可以把第二个a,我们简称a2,看作有左边界,分别为l,a2重复出现,就把l设置为它上一次出现位置的下一位;如果a未出现过,就把左边界设为0,那么我们就可以得到如下的当前字母贡献值公式

  当前字母未出现过:(下标+1)*(字串长度-下标)

  当前字母出现过了:(当前位置-上一次出现的位置)*(字串长度-下标)

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

int main()
{
	long long n,i;
	int a[26]={0};	//0代表每个字母均未出现过 
	char s[100000];
	scanf("%s",s);
	
	long long sum=0;
	n=strlen(s);
	for(i=0;i<n;i++)
	{
		if(a[s[i]-'a']==0)	//当前字母未出现过 
		{
			sum+=(i+1)*(n-i);	//贡献的值=(下标+1)*(s长度-下标) 
			a[s[i]-'a']=i+1;	//把a的位置记录下来 
		}
		else{	//当前字母出现过 
			sum+=(i+1-a[s[i]-'a'])*(n-i);	//贡献的值=(当前位置-前一个位置)*(s长度-下标) 
		}
	}
	printf("%lld",sum);
}

以上均是个人借鉴了各位大佬的方法总结出的一些思路,十分简洁清晰,如有不足之处,多多包涵,欢迎指正!!! 

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值