题目描述
对于一个字符串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-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%。