资源限制
内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s
问题描述对于一个字符串 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
输入一行包含一个由小写字母组成的字符串 S。
输出一个整数表示答案。
ababc
Data
样例输出21
Data
样例说明子串 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
None
评测用例规模与约定对于 20% 的评测用例,1≤n≤10;
对于 40% 的评测用例,1≤n≤100;
对于 50% 的评测用例,1≤n≤1000;
对于 60% 的评测用例,1≤n≤10000;
对于所有评测用例,1≤n≤100000。
dfs非常明显不能AC,搜索树深度太深...但是比赛的时候能骗一大部分分......
本来还想用map进行记忆化搜索,没想到过的测试点还少了一组,非常尴尬......
贴上AC一半的dfs代码,非常简短和容易想到。
#include
using namespace std;
string s;
int a[30];
long long sum;
int check(string t){//统计字符串中只出现了一次的字母的个数
int cnt=0;
memset(a,0,sizeof(a));
for(int i=0;i>s;
string t=s;
for(int i=0;i
s=t;//还原s
}
cout<
#include
using namespace std;
string s;
int a[30];
long long sum;
int cnt;
int check(string t){
int cnt=0;
memset(a,0,sizeof(a));
for(int i=0;im,vis;
int fun(string str){
if(!str.size())return 0;
if(m[str]){//此字符串已经搜索过
return m[str];
}
m[str]+=fun(str.substr(0,str.length()-1));
m[str]+=check(str);
return m[str];
}
int main(){
cin>>s;
string t=s;
string str=" ";
for(int i=0;i
法二:尺取法 AC版
乘法原理 O ( N ) :
解题思路:
统计每个字母在仅出现一次的情况下,能被多少子串所包含;
用 pre[i] 记录第 i 个字母上一次出现的位置,用 next[i] 记录第 i 个字母下一次出现的位置;
那么对于第i个字母,
其往左最多能延伸到 pre[i] + 1,到第 i 个字母一共有 i - pre[i] 个字母; 同理,a和c之间可以取0到c-a-1个字符,即c-a种方法。 两者相乘即是a作为某字串中唯一的字符时的贡献。 运用了简单的计数原理。
同理往右最多能延伸到 next[i] - 1,到第 i 个字母一共有 next[i] - i 个字母;
二者相乘,就是该字母被不同子串所包含的总次数;
再换种说法分析,即对于字符串中的每个字符,我们可以考虑它对答案的贡献度,即 它 所能在的所有的字符子串中,它是唯一的个数,设某个字符位置位a,和它相同的前一个字符位置为b,和它相同的后一个字符位置为c,那么他的贡献度为(a-b)*(c-a),其中a-b种取法是在a和b之间有a-b-1个字母,(不包括端点,因为端点是a和b,a必取,b必不取,所以不算在取法中)可以取0到a-b-1个字符。
为了加快时间,我们预处理每个字符的上一个位置和下一个位置。
#include
#define ll long long
using namespace std;
const int N = 1e5 + 5, M = 1e5 + 5;
int pre[N], ne[N], ans;//pre[i]的值即 对于下标为i的字符的前一个相同字符的位置
int last[30];//记录某字母最后一次出现的位置
int main() {
string s;
getline(cin, s);
int len = s.length();
memset(last, -1, sizeof(last));//前面没有就是-1,这样0-(-1)是1
for (int i = 0; i < len; i++) {
pre[i] = last[s[i] - 'a'];//每个字符-'a'相当于得到它和'a'的距离,可以作为独特下标索引
//因为循环会不断的更新pre和last数组,所以pre[i]最终存的就是
//前面一个和自己相同的字符的下标,即是上一次更新后的最后的此字符位置
last[s[i] - 'a'] = i; //更新s[i]字符最后一次出现的位置
}
for (int i = 0; i < 26; i++) last[i] = len;//如果后面没有就是len。
因为最多只能从最后一位选
//为什么要倒着推,因为我们要知道x字符的下一个位置,必然需要提前知道他后面的
for (int i = len - 1; i >= 0; i--) {
ne[i] = last[s[i] - 'a'];//与pre的更新同理,倒着更新使得ne[i]存着对于i下标字符而言最近的下一个字符的下标
last[s[i] - 'a'] = i; //更新s[i]字符最后一次出现的位置
}
for (int i = 0; i < len; i++)
ans += (i - pre[i]) * (ne[i] - i);
//i点的上一个相同字符距离x,下一个位y,
//左边可以取x种(1-x),右边y种,共x*y
cout << ans;
return 0;
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)