蓝桥杯--子串分值

题目描述

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

现在给定一个字符串S0⋯n−1​(长度为 n,1≤n≤10^5,请你计算对于所有 S 的非空子串 Si⋯j​(0≤i≤j

输入描述

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

输出描述

输出一个整数表示答案。

输入输出样例

示例

输入

ababc

输出

21

运行限制

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

思路一:最简单粗暴的想法:使用两个for循环进行暴力枚举。可以该时间复杂度为O(n^2),在数据量级为10^5时,“暴力”显得不可行。

思路二:利用每个字母的贡献值

 根据题目一个字符串中一旦出现两个一样的字符时,其贡献值就变为0,即无效贡献字符串

也就是说,一个字母的有效贡献字符串长度被离其最近的前后两个同字母限定。

以ababa为例子。

0 1 2 3 4
a b a b a

对于(2,a)来说,它有限贡献字符串被(0,a)与(4,a)框定,也就是bab的含a子串

而这些子串的个数公式为(2-0)*(4-2),抽象化后(i-pre[i])*(next[i]-i);含义就是以a中心将bab分为含中心的两个区域,(ba],[ab)。两个区域的个数之积即为所求。

位置编号 字母元素 字串列举 贡献值 公式计算
0 a

a

ab

2 (0-(-1))*(2-0)
1 b

b

ab

ba

aba

4 (1-(-1))*(3-1)
2 a

a

ba

ab

abc

bab

babc

6 (2-0)*(5-2)
3 b

b

ab

bc

abc

4 (3-1)*(5-3)
4 c

c

bc

abc

babc

ababc

5 (4-(-1))*(5-4)
合计 21 21

因此代码将分为以下几个部分:

1.每个编号字母的前后最近同字母位置的初始化。

2.标记每个字母的前后最近字母位置

3.开始计算累和。

#include
using namespace std;
int forth[100003];
int latter[100003];
int main()
{
	int sum=0,target;//target就是基准
	char a[100003];
	int mark[27];
	int lmark[27];
	gets(a);//输入那些字符串
	int length=strlen(a);//计算长度
	for(int i=0;i<26;i++){
		mark[i]=-1;//初始化为-1
		lmark[i]=length;//初始化为字符串长度
	}
	for(int i=0;i=0;i--){
		target=a[i]-'a';//将字符转换为字符编号
		latter[i]=lmark[target];//该字母的后面(离该字母最近的)又出现该字母的位置编号给予它
		lmark[target]=i;//更新字符的位置
	}
	for(int i=0;i

该思路以及代码来自(1条消息) 子串分值(解决sum+=(i-pre[i])*(next[i]-i)的真谛与疑惑)_张立龙666的博客-CSDN博客

思路二将时间复杂度降到O(3*n)~O(4*n)也就是O(n)级别。但是空间存储稍微有一点大。于是我们来看看下面这个思路

思路三:dp+分治思想

以ababc而言,面对它我们可以认为是f(S(不含尾字母的子串))之和+f(S(含为字母字串))之和。

也就是从ababc(级别5)-->abab(级别4)-->aba(级别3)-->ab(级别2)-->a(级别1)。

我们可以列出以下表格

字符串 增加字符串 增加贡献值(d[i])
a a 1
ab b,ab 3
aba a,ba,aba 4
abab b,ab,bab,abab 4
ababc c,bc,abc,babc,ababc 9
合计 21

从上表我们可以发现:d[i] = d[i-1] + i - pre[i]*2 ;(i从1开始)

然而我们很自然的想到似乎还不对,例如下表列出ababb的情况;

字符串 增加字符串 增加贡献值
a a 1
ab b,ab 3
aba a,ba,aba 4
abab b,ab,bab,abab 4
ababb b,bb,abb,babb,ababb 3
合计 15

我们马上发现当前面的字符一旦出现多个同样的字母,那么字母带来的效果不再是单单的一个“-1”,还可能保持不变。

其实很容易理解,一个字符串里面有两个相同字母==>分值-1,而两个以上==>分值+0;

也就有下式:

d[i] = d[i-1] + i - pre[i] - ( pre[i] - pre[pre[i]] );

式子注解:(i - pre[i])这是真正+1的部分,碰见第一个相同字符前;

                 ( pre[i] - pre[pre[i]] )这是-1的部分,在碰见前前一个相同字母前;

代码如下:(因为下标从0开始,所以上面描述的所有i均-1,也就是现在的范围是[-1,n-1])

#include
#include
int d=0;
int len=0;
char a[100001];
int atozlen[26][2];
unsigned int sum=0;
int main() 
{    
    int i,x,y;
  int eq1;
    int eq2;
    gets(a);
    len=strlen(a);
    for(x=0;x<26;x++)
        for(y=0;y<2;y++)
            atozlen[x][y]=-1;
    for(i=0;i

你可能感兴趣的:(蓝桥杯,职场和发展,算法,c++,c语言)