前言
实际上这是蓝桥真题,和子串分值和一样
子串分值和
题目描述
对于一个字符串 S {S} S,我们定义 S {S} S的分值 f ( S ) {f(S)} f(S)为 S {S} S中出现的不同的字符个数。例如 f ( " a b c " ) = 3 , f ( " a b a " ) = 2 , f ( " a a a " ) = 1 {f("abc") = 3,f("aba") = 2,f("aaa") = 1} f("abc")=3,f("aba")=2,f("aaa")=1。
现在给定一个字符串 S {S} S,请你计算所有 S {S} S的非空子串 S [ i . . . j ] ( 0 ≤ i ≤ j < n ) {S[i...j](0≤i≤j<n)} S[i...j](0≤i≤j<n), f ( S [ i . . . j ] ) {f(S[i...j])} f(S[i...j])的和是多少。
输入描述
输入一行包含一个由小写字母组成的字符串
S
{S}
S。
其中,
1
≤
n
≤
1
0
5
{1≤n≤10^5}
1≤n≤105。
输出描述
输出一个整数表示答案。
样例
输入#1
ababc
输出#1
28
输入#2
code
输出#2
20
思路
显然这是一个典型 n 2 {n^2} n2题,但是 1 0 5 {10^5} 105的数据不可能过。那么这个时候就要转换思路了。
我们可以考虑对于每一个字符 S i {S_i} Si,它对最终的结果做出的贡献 W i {W_i} Wi 是多少,那么答案自然是 ∑ i = 0 n − 1 W i {\sum\limits_{i=0}^{n-1}}W_i i=0∑n−1Wi。
在此,我们定义贡献wi为:包含当前字符且
当前字符之前不在含有该字符
的所有字符串的个数。
这是我自己给的定义,应该很抽象。
以样例一举例说明: a b a b c {ababc} ababc,第二个 a {a} a对答案的贡献是多少?
可以算得结果是 6 {6} 6:
ba
bab
babc
a
ab
abc
为什么这样是对的?(也就是左边有限制(到左边过去第一个同字符),右边无限制(一直到字符串末尾))
不会证明,但是如果前面还含有该字符,当前字符是不在产生贡献的。(因为在计算之前那个字符的时候,已经包含了当前字符的情况)。而且这样不重不漏的包含了所有情况数。
题解
法一:计算贡献
- 我们用 a {a} a数组来记录字符最后出现的位置。
- 下标从 0 {0} 0开始,所以对于字符第一次出现,左侧应该是 i + 1 {i + 1} i+1 个字符,那么初始化 a {a} a数组为 − 1 {-1} −1就很好的解决了。
class Solution {
public:
long long appealSum(string s) {
int n = s.size();
int a[26];
memset(a, -1, sizeof a);
long long res = 0;
for (int i = 0; i < n; i++) {
int x = s[i] - 'a';
res += (i - a[x]) * (n - i);
a[x] = i;
}
return res;
}
};
法二:动态规划
-
定义 f [ i ] {f[i]} f[i]:表示以 i {i} i为结尾的所有子字符串的产生的贡献值。
-
对于 S [ 0... i − 1 ] {S[0...i-1]} S[0...i−1]所有的贡献都会计入 S [ 0... i ] {S[0...i]} S[0...i],即 f [ i − 1 ] {f[i-1]} f[i−1]
-
设 S j = = S i {S_j==S_i} Sj==Si:那么以 S i {S_i} Si结尾的字符,不会对 S [ 0... j ] {S[0...j]} S[0...j]的之前产生贡献,被 S j {S_j} Sj顶替了。但对 S [ j + 1... i ] {S[j +1...i]} S[j+1...i]之间的字串能产生 1 {1} 1 点贡献,即 i − j {i-j} i−j。
-
递推方程: f [ i ] = f [ i − 1 ] + i − j {f[i] = f[i - 1] + i -j} f[i]=f[i−1]+i−j
-
-
答案: ∑ i = 0 n − 1 f i {\sum\limits_{i=0}^{n-1}f_i} i=0∑n−1fi
class Solution {
public:
long long appealSum(string s) {
int n = s.size();
vector<int> a(26, -1);
vector<int> f(n, 0);
f[0] = 1;
a[s[0] - 'a'] = 0;
for (int i = 1; i < n; i++) {
int x = s[i] - 'a';
f[i] = f[i - 1] + i - a[x];
a[x] = i;
}
long long res = 0;
for (int i = 0; i < n; i++) res += f[i];
return res;
}
};
优化掉一维数组:
class Solution {
public:
long long appealSum(string s) {
vector<int> a(26, -1);
long long num = 0, res = 0;
for (int i = 0; i < s.size(); i++) {
int x = s[i] - 'a';
num += i - a[x];
res += num;
a[x] = i;
}
return res;
}
};
子串分值
题目描述
对于一个字符串 S {S} S,我们定义 S {S} S的分值 f ( S ) {f(S)} f(S)为 S {S} S中恰好出现一次的字符个数。例如 f ( " a b c " ) = 3 , f ( " a b a " ) = 1 , f ( " a a a " ) = 0 {f("abc") = 3,f("aba") = 1,f("aaa") = 0} f("abc")=3,f("aba")=1,f("aaa")=0。
现在给定一个字符串 S {S} S,请你计算所有 S {S} S的非空子串 S [ i . . . j ] ( 0 ≤ i ≤ j < n ) {S[i...j](0≤i≤j<n)} S[i...j](0≤i≤j<n), f ( S [ i . . . j ] ) {f(S[i...j])} f(S[i...j])的和是多少。
输入描述
输入一行包含一个由小写字母组成的字符串 S {S} S。
输出描述
输出一个整数表示答案。
样例
输入#1
ababc
输出#1
21
思路
-
计算单点贡献,由于是恰好出现一次,所以考虑左右同字符。(即左右都有限制)
-
S l = = S i ( l < i ) {S_l==S_i(l<i)} Sl==Si(l<i) , S r = = S i ( r > i ) {S_r==S_i(r>i)} Sr==Si(r>i)
- 贡献: ( i − l ) ∗ ( r − i ) {(i-l)*(r-i)} (i−l)∗(r−i)
-
考虑一下首尾的特殊情况,即左或右没有。那么我是对字符下标数组左右加上 0 {0} 0 和 n + 1 {n+1} n+1
题解
#include <bits/stdc++.h>
using namespace std;
int main() {
string s; cin >> s;
int n = s.size();
vector<int> a[26];
s = '@' + s;
for (int i = 1; i <= n; i++) {
int x = s[i] - 'a';
if (a[x].size() == 0) a[x].push_back(0);
a[x].push_back(i);
}
for (int i = 0; i < 26; i++) {
if (a[i].size()) a[i].push_back(n + 1);
}
int res = 0;
for (int i = 0; i < 26; i++) {
auto& v = a[i];
if (!v.size()) continue;
for (int j = 1; j < v.size() - 1; j++)
res += (v[j] - v[j - 1]) * (v[j + 1] - v[j]);
}
cout << res << endl;
return 0;
}