#算法02 枚举贡献

本文介绍了如何使用贡献法优化算法,通过枚举元素对子串的贡献,降低子串枚举的时间复杂度,解决字符串子串分值问题,如2868.子串分值-AcWing题库中的例子。
摘要由CSDN通过智能技术生成

#2024-03-08 贡献法

1、原理

枚举贡献用于求解一个字符串所有子串的某种性质,或者一个大区间中所有子区间的某种性质。因为子串或者子区间的枚举时间复杂度为O(n^2),所以改变思路,枚举字符串或区间中所有元素对子串或子区间对应性质的贡献,这样的话时间复杂度大概被优化为O(n)。通过把所有元素的贡献求和便可解决问题。

2、例题讲解

例题:2868. 子串分值 - AcWing题库

大致题意:对于一个全为小写字母组成的字符串S,我们定义S的分值f(S)为S中恰好出现一次的字符个数,求S中所有非空子串的分值和。

                                     *****a*****a********a****

以上面的字符串为例,计算红色a(后面用A来表示)的贡献值,同理可以枚举剩下的25个字母。字母要想产生贡献,相应子串必须包含该字母,显然只有三种情况:

1、A是子串的左端点;2、A是子串的右端点;3、A是子串的一部分但不是端点。

1对应的贡献值就是A左侧不是A的字母数,同理2对应的贡献值就是A右侧不是A的字母书。

3对应的贡献值就是包含A的子串数,即在A左侧枚举左端点,在右侧枚举右端点,等于

(A左侧不是A的字母数) * (A右侧不是A的字母数)。

#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int num[N], l[30][N], r[30][N];
char str[N];

int main()
{
    scanf("%s", str);
    int len = strlen(str);
    LL res = len; // 所以字母单独出现一次都是分值为1的非空子串
    for (int i = 0; i < len; i ++)
        num[i] = str[i] - 'a'; // 将字母变成数字处理
    
    for (int i = 0; i < 26; i ++) // 字符串中可能包含26种字母
    {
        for (int j = 0, cnt = 0; j < len; j ++) // 用cnt动态存储当前字母左侧相同字符的距离
        {
            if (num[j] == i) l[i][j] = cnt, cnt = 0;
            else cnt ++;
        }
        for (int j = len - 1, cnt = 0; j >= 0; j --) // 用cnt动态存储当前字母右侧相同字符的距离
        {
            if (num[j] == i) r[i][j] = cnt, cnt = 0;
            else cnt ++;
        }
    }
    
    for (int i = 0; i < 26; i ++)
    {
        for (int j = 0; j < len; j ++)
        {
            res += (LL)l[i][j] * r[i][j] + l[i][j] + r[i][j]; // 计算每个字母的贡献
        }
    }
    printf("%lld\n", res);
    return 0;
}

后续修改:l和r数组由于是按位置储存,所以不需要开二维数组。

#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int num[N], l[N], r[N];
char str[N];

int main()
{
    scanf("%s", str);
    int len = strlen(str);
    LL res = len;
    for (int i = 0; i < len; i ++)
        num[i] = str[i] - 'a';
    
    for (int i = 0; i < 26; i ++)
    {
        for (int j = 0, cnt = 0; j < len; j ++)
        {
            if (num[j] == i) l[j] = cnt, cnt = 0;
            else cnt ++;
        }
        for (int j = len - 1, cnt = 0; j >= 0; j --)
        {
            if (num[j] == i) r[j] = cnt, cnt = 0;
            else cnt ++;
        }
    }
    
    for (int j = 0; j < len; j ++)
    {
        res += (LL)l[j] * r[j] + l[j] + r[j];
    }

    printf("%lld\n", res);
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值