对于一个字符串 S,我们定义 S 的分值 f(S) 为 SS 中恰好出现一次的字符个数。例如 f(aba)=1,f(abc)=3,f(aaa)=0。
现在给定一个字符串 S0⋯n−1(长度为 n,1≤n≤105),请你计算对于所有 S 的非空子串 Si⋯j(0≤i≤j<n),f(Si⋯j) 的和是多少。
输入一行包含一个由小写字母组成的字符串 S。
输出一个整数表示答案。
输入
ababc
输出
21
子串 f值
a 1
ab 2
aba 1
abab 0
ababc 1
b 1
ba 2
bab 1
babc 2
a 1
ab 2
abc 3
b 1
bc 2
c 1
这道题用暴力法只能得到一般的分数,运行会超时,所以只能计算每个字母的贡献值,这样可以节约运行时间。
要想计算贡献值,对每一个字母 x[i],找到前一个相同的字母下标 pre[i],后一个相同字母的下标 next[i],然后用贡献值的计算公式
(i-pre[i])*(next[i]-i) (这个公式计算的是一个字母的贡献值,将所有的字母的贡献值加在一起就是结果了) 这个算法是计算字符串中每个字符的贡献值即影响因子,将贡献值累加就得到了结果。那么该如何来计算每个字符的贡献值呢?
所谓贡献值,即该字符能够影响到的子串个数,就比如
0 1 2 3 4 5
b a c b e b
表格中的3号b能够影响多少个子串呢?
列举一下有:acbe,acb,cbe,cb,b,be共有6个(这些子串的f值都因为b增加了1,而 bacb这个子串不会因为3号b增加f值).
那么如何用数学的方法计算出来贡献值呢?
这些子串的起点可以为1、2、3,终点可以为3、4,即(3-0)(5-3) = 6
其中 0 是 3号 b往左遇到的第一个b的标号,而 5是 3号 b往右遇到的第一个 b的标号。
!!!!!推论:一个字符(位置为i)的影响范围由其往左边遍历遇到的第一个相同字符(位置为left)和
其往右边遍历遇到的第一个相同字符(位置为right)所决定。其贡献值为(i - left)*(right - i)
乘法原理 O ( N ):解题思路:
1、统计每个字母在仅出现一次的情况下,能被多少子串所包含;
2、用 pre[i] 记录第 i 个字母上一次出现的位置,用 next[i] 记录第 i 个字母下一次出现的位置;
3、那么往左最多能延伸到 pre[i] + 1,其到第 i 个字母一共有 i - pre[i] 个字母;
4、同理往右最多能延伸到 next[i] - 1,其到第 i 个字母一共有 next[i] - i 个字母;
5、二者相乘,就是该字母被不同子串所包含的总次数;
#include
#include
#define N 100002
int main()
{
char x[N];
int pre[N]; //存放该位置的前一个相同字母的位置
int next[N]; //放该位置的后一个相同字母的位置
int late[27]; //储存该字母的索引
int sum=0;
scanf("%s",x);
int len=strlen(x);
//求该字母的前一个相同的字母的位置
for(int i=0;i<=26;i++)
late[i]=-1; //初始化每个字母的前序索引为 x 开始的前一个位置,也就是 -1
for(int i=0;i<len;i++)
{ //找到每个位置的前一个相同字母的位置
int m=x[i]-'a';
pre[i]=late[m];
late[m]=i;
}
//求该字母的后一个相同的字母的位置
for(int i=0;i<=26;i++)
late[i]=len; //初始化每个字母的后序索引为 x 结束的后一个位置,也就是 n
for(int i=len-1;i>=0;i--)
{ //找到每个位置的后一个相同字母的位置
int m=x[i]-'a';
next[i]=late[m];
late[m]=i;
}
//累加每个字母的贡献值
for(int i=0;i<len;i++)
sum+=(i-pre[i])*(next[i]-i);
printf("%d",sum);
return 0;
}