蓝桥杯2020年第十一届省赛真题-子串分值

题目描述

对于一个字符串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]) 的和是多少。

输入格式

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

输出格式

输出一个整数表示答案。

样例输入

ababc

样例输出

21

提示

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

解决思路

这次想用数学归纳法的思路去解决子串问题,先看下面:

设需要计算子串分值的字符串为s

设F为字符串s的总“子串分值”

设一个sf数组,sf[i] 记录的是 s[i:] 这一个字符串的“分值”(理解这一点很重要),那么:

s='a'
sf=[1]

F=0+(1)=1


s='ab'
sf=[2,1]

F=1+(1+2)=4


s='aba'
sf=[1,2,1]

F=4+(1+2+1)=8


s='abab'
sf=[0,1,2,1]

F=8+(1+2+1+0)=12


s='ababc
sf=[1,2,3,2,1]

F=12+(1+2+3+2+1)=21

……

现在看不懂还没关系,我们将黑色的部分称为“前子串”,红色的部分称为新加入的字母

我们发现,每多增加一个字母,都需在前一轮“子串分值”(也就是黑色部分的子串分值)的基础上,再加上每个新子串的分值。

注意,新子串是由s[n-1](红色部分)的新加入所产生的。以s='ababc'为例,加入新字母c后,产生的新子串有:c、bc、abc、babc、ababc。

而我的sf数组中,记录的正是这些新子串的“分值”。再以s='ababc'为例,sf=[1,2,3,2,1] 的含义是:“ababc”分值1、“babc”分值2、“abc”分值3、“bc”分值2、“c”分值1。

sf[i] 记录的是 s[i:] 这一个字符串的“分值”,理解这一点真的很重要。

所以不难发现,一个长度为n的字符串,子串分值 = 字符串[ : n-1]的子串分值 + 产生的新子串们的分值和:

F(n)=F(n-1)+SUM(sf)

F(n-1)是由前一轮的计算得知,而sf数组又是如何得到的呢?再看下面:

sf数组也可以由上一轮的sf数组结果得到,有三种情况:

(1)若新增的字母,不在前子串中,那么增加它会使整个字符串的分值加一

(2)若新增的字母,在前子串中,且前子串中有且仅有一个它,那么增加它会使整个字符串的分值减一

(3)若新增的字母,在前子串中,且有两个以上的它,那么增加它不会影响字符串的分值

例如,“abb”的分值是1,若加c,则“abbc”的分值是1+1=2;若加a,则“abba”的分值是1-1=0;若加b,则“abbb”的分值和“abb”保持一致,是1。

再看下面的具体过程: 

s='a'

产生新子串'a',分值为1
所以sf=[1]


s='ab'

产生新子串'b',分值为1

产生新子串'ab',分值为1+1=2   # 前1是上一轮s='a'的分值,后1是因为加'b'后满足情况(1)
所以sf=[2,1]


s='aba'

产生新子串'a',分值为1

产生新子串'ba',分值为sf[1]+1=2    # 满足情况(1)

产生新子串'aba',分值为sf[0]-1=1    # 满足情况(2)
所以sf=[1,2,1]


s='abab'

产生新子串'b',分值为1

产生新子串'ab',分值为sf[2]+1=2    # 满足情况(1)

产生新子串'bab',分值为sf[1]-1=1    # 满足情况(2)

产生新子串'abab',分值为sf[0]-1=0    # 满足情况(2)

所以sf=[0,1,2,1]


s='ababc

产生新子串'c',分值为1

产生新子串'bc',分值为sf[3]+1=2    # 满足情况(1)

产生新子串'abc',分值为sf[2]+1=3    # 满足情况(1)

产生新子串'babc',分值为sf[1]+1=2    # 满足情况(1)

产生新子串'ababc',分值为sf[0]+1=1    # 满足情况(1)

所以sf=[1,2,3,2,1]

sf 数组就是这么计算而来。

根据此数学归纳,我们基本可以写出代码:

s = input()

F=0
sf=[]

for i in range(len(s)):
    sf.append(1)
    for j in range(i,-1,-1):
        if i==j:
            pass
        elif s[i] not in s[j:i]:  # 情况(1)
            sf[j] += 1
        else:
            if s[j:i].count(s[i])==1:  # 情况(2)
                sf[j] -= 1
    F = F+sum(sf)

print(F)

后来觉得每次加的都是sum(sf),不如直接令sf为sum(sf):

s = input()

F=0
sf=0

for i in range(len(s)):
    for j in range(i,-1,-1):
        if s[i] not in s[j:i]:  # 情况(1)
            sf += 1
        elif s[j:i].count(s[i])==1:  # 情况(2)
                sf -= 1
    F = F+sf

print(F)

最后又优化了一下:

s = input()

F=0
sf=0

for i in range(len(s)):
    case = 1
    sf += 1
    for j in range(i-1,-1,-1):
        # s[i]不在前子串内 情况(1)
        if case==1:
            if s[j]!=s[i]:  # 依旧不在子串内
                sf += 1
            else:
                case = 2
                sf -= 1
        # s[i]在前子串内 有且仅有一个 情况(2)
        elif case==2:
            if s[j]!=s[i]:
                sf -= 1
            else:
                case = 3
    F = F+sf

print(F)

但是无论怎么优化,通过率都是50%,剩下的都超时了。

然后我用C语言写了一下:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int N = 100000;
	char s[N];
	scanf("%s",&s);
	
	int F=0;
	int dp=0;
	int cas=1;
	
	int i=0;
	while(s[i]!=0){
		
		cas = 1;
		dp ++;
		for(int j=i-1;j>=0;j--)
        {
			if(cas==1){
				if(s[j]!=s[i]){
					dp++;
				}
				else{
					cas = 2;
					dp--;
				}
			}
			else if(cas==2){
				if(s[j]!=s[i]){
					dp--;
				}
				else{
					cas=3;
				}
			}
		}
		F = F+dp;
		i++;
	}
	
	printf("%d",F);

    return 0;
}

发现通过率100%。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值