蓝桥杯--子串分值

题目描述

对于一个字符串 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<n),f(Si⋯j​) 的和是多少。

输入描述

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

输出描述

输出一个整数表示答案。

输入输出样例

示例

输入

ababc

输出

21

运行限制

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

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

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

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

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

以ababa为例子。

01234
ababa

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

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

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

a

ab

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

b

ab

ba

aba

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

a

ba

ab

abc

bab

babc

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

b

ab

bc

abc

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

c

bc

abc

babc

ababc

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

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

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

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

3.开始计算累和。

#include<bits/stdc++.h>
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<length;i++){
		target=a[i]-'a';//将字符转换为字符编号
		forth[i]=mark[target];//该字母的前面(离该字母最近的)又出现了该字母的位置编号给予它
		mark[target]=i;//更新字符的位置
	}
	for(int i=length-1;i>=0;i--){
		target=a[i]-'a';//将字符转换为字符编号
		latter[i]=lmark[target];//该字母的后面(离该字母最近的)又出现该字母的位置编号给予它
		lmark[target]=i;//更新字符的位置
	}
	for(int i=0;i<length;i++){
		sum=sum+((i-forth[i])*(latter[i]-i));
	}
	cout<<sum;
	return 0;
}

该思路以及代码来自(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])
aa1
abb,ab3
abaa,ba,aba4
ababb,ab,bab,abab4
ababcc,bc,abc,babc,ababc9
合计21

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

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

字符串增加字符串增加贡献值
aa1
abb,ab3
abaa,ba,aba4
ababb,ab,bab,abab4
ababbb,bb,abb,babb,ababb3
合计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<stdio.h>
#include<string.h>
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<len;i++)
    {
        eq1=atozlen[a[i]-'a'][0];
          eq2=atozlen[a[i]-'a'][1];
        if(atozlen[a[i]-'a'][0]==-1)
            atozlen[a[i]-'a'][0]=i;
        else 
        {
            atozlen[a[i]-'a'][1]=atozlen[a[i]-'a'][0];
            atozlen[a[i]-'a'][0]=i;
        }
        d+=i-eq1-(eq1-eq2);
        sum=sum+d;
    }
    printf("%u",sum);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值