题目描述
对于一个字符串 S,我们定义 S 的分值 f(S) 为 S 中出现过一次的字符个数。例如 f(aba)=2,f(abc)=3,f(aaa)=1。
现在给定一个字符串 S0.....Sn-1(长度为 n,1≤n≤100000),请你计算对于所有 S 的非空子串 Si⋯j(0≤i≤j<n),f(Si⋯j) 的和是多少。
输入描述
输入一行包含一个由小写字母组成的字符串 S。
输出描述
输出一个整数表示答案。
输入输出样例
示例
输入
ababc
输出
21
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
举个例子:f(ababc)=28
a 1
ab 2
aba 2
abab 2
ababc 3
b 1
ba 2
bab 2
babc 3
a 1
ab 2
abc 3
b 1
bc 2
c 1
第一种方法 穷举法(暴力破解)
就是把每个字串的f值算出来相加,这样用了两个for循环,那么时间复杂度,只能拿40.50分。
#include<bits/stdc++.h>
using namespace std;
int a[27];
int check(int x)
{
if(a[x]==1)
return 1;
if(a[x]==0)
{
a[x]=1;
return 0;
}
}
int fsum(char *str)
{
int i,j,k;
char c[100000];
strcpy(c,str);
int sum=0;
int num;
for(j=0;j<strlen(str);j++)
{
for(k=1;k<27;k++)
a[k]=0;
num=0;
for(i=j;i<strlen(str);i++)
{
if(check(c[i]-96)==0)
{
num++;
}
sum+=num;
}
}
return sum;
}
int main()
{
char *str={"ababc"};
cout<<fsum(str);
}
第二种方法 动态规划
设置一个dp[i]数组来保存第i位字母贡献的值,来看下图:
由图我们可以清晰的总结出,第i位字母贡献的值总是等于第i-1位贡献的值+第i位字母的位置-第i位字母上一次出现的位置(如果未出现则为0),即dp数组表达式为:dp[i]=dp[i-1]+i-a[s[i]-'a']
用a[i]来代表26位字母出现的位置,其中i=0~25,分别对应了a~z。如a[1]=0,表示b之前在第0位出现过。
故只需一遍循环就可得出结果,那么时间复杂度为O(n)。
#include<bits/stdc++.h>
using namespace std;
int a[26]={0};
int main()
{
char s[100000];
long long sum,n,i;
int dp[100000];
scanf("%s",s);
dp[0]=0;
sum=0;
n=strlen(s)+1;
for(i=1;i<n;i++)
{
dp[i]=dp[i-1]+i-a[s[i-1]-'a'];
a[s[i-1]-'a']=i;
sum+=dp[i];
}
printf("%lld",sum);
}
第三种方法 乘法原理
不从每一个字串的f值相加这个方向考虑,而是从ababc中每个字母贡献的值来考虑,从而把加法转化为乘法。
还是以ababc为例,可以看到第一个a在所有子串种出现了5次,所以它贡献值为5;第二个b出现了8次,所以它的贡献值为8;重点为第三个a,它虽然在所有子串中出现了9次,但是有其中的三个字串中包含了第一个a,并且这第一个a的贡献值已经被计了,故第二个a的贡献值不是9,而是6。
以此类推,我们得到f值的和为5+8+6+4+5=28。
到这肯定有些同学会疑问,那后面重复出现的字母贡献值如何计算?
可以把第二个a,我们简称a2,看作有左边界,分别为l,a2重复出现,就把l设置为它上一次出现位置的下一位;如果a未出现过,就把左边界设为0,那么我们就可以得到如下的当前字母贡献值公式
当前字母未出现过:(下标+1)*(字串长度-下标)
当前字母出现过了:(当前位置-上一次出现的位置)*(字串长度-下标)
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long n,i;
int a[26]={0}; //0代表每个字母均未出现过
char s[100000];
scanf("%s",s);
long long sum=0;
n=strlen(s);
for(i=0;i<n;i++)
{
if(a[s[i]-'a']==0) //当前字母未出现过
{
sum+=(i+1)*(n-i); //贡献的值=(下标+1)*(s长度-下标)
a[s[i]-'a']=i+1; //把a的位置记录下来
}
else{ //当前字母出现过
sum+=(i+1-a[s[i]-'a'])*(n-i); //贡献的值=(当前位置-前一个位置)*(s长度-下标)
}
}
printf("%lld",sum);
}
以上均是个人借鉴了各位大佬的方法总结出的一些思路,十分简洁清晰,如有不足之处,多多包涵,欢迎指正!!!