6050. 字符串的总引力(子串分值和)

6050. 字符串的总引力

前言

实际上这是蓝桥真题,和子串分值和一样


子串分值和

题目描述

对于一个字符串 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](0ijn) f ( S [ i . . . j ] ) {f(S[i...j])} f(S[i...j])的和是多少。

输入描述

输入一行包含一个由小写字母组成的字符串 S {S} S
其中, 1 ≤ n ≤ 1 0 5 {1≤n≤10^5} 1n105

输出描述

输出一个整数表示答案。

样例

输入#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=0n1Wi

在此,我们定义贡献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...i1]所有的贡献都会计入 S [ 0... i ] {S[0...i]} S[0...i],即 f [ i − 1 ] {f[i-1]} f[i1]

    • 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} ij

    • 递推方程: f [ i ] = f [ i − 1 ] + i − j {f[i] = f[i - 1] + i -j} f[i]=f[i1]+ij

  • 答案: ∑ i = 0 n − 1 f i {\sum\limits_{i=0}^{n-1}f_i} i=0n1fi

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](0ijn) 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)} (il)(ri)
  • 考虑一下首尾的特殊情况,即左或右没有。那么我是对字符下标数组左右加上 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;
}

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ღCauchyོꦿ࿐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值