#P2696. 第3题-字符串权值
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 147
            Accepted: 29
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              美团
                                
            
                        
              时间 :2025年3月15日-算法岗
                              
                      
          
 
- 
                        算法标签>前缀和          
 
第3题-字符串权值
思路: 数学优化 + 哈希表
暴力思路:对于一个位置i,我们考虑往前找所有s[i] == s[j] 的位置j。那么[j+1,i-1]这一段的任意一个字符都可以被选择,也就是j对i的答案贡献就是j - i - 1。也就是找到所有的j,去求和。
那么位置i的答案是:

发现第一个求和式的内容(i - 1) 与 j无关:

答案就是 prefix[i] = prefix[i - 1] + ans_i
优化
根据上述式子我们发现
左边的求和式就是: [1 , i - 1] 内 与s[i] 相同的 数 的 个数
右边的求和式就是:[1 , i - 1] 内 与s[i] 相同的 数 的 下标和
那么我们在代码中使用哈希表mp[i] 维护 当前为止i出现的次数 以及 sm[i] 维护 当前i这个字符出现的下标和。
维护方法:每读到一个字符s[i], mp[s[i]] += 1 , sm[s[i]] += i
复杂度:O(n) , 哈希的更新是O(1)的
Python代码
n = int(input())
s = input()
mp = {}
sm = {}
dp = [0] * (n + 1)
for i in range(1 , n + 1):
    cnt = mp.get(s[i - 1] , 0) 
    res = 0
    if cnt != 0:
        res = (i - 1) * cnt - sm.get(s[i - 1] , 0)
    dp[i] = dp[i - 1] + res
    mp[s[i - 1]] = cnt + 1
    sm[s[i - 1]] = sm.get(s[i - 1] , 0) + i
print(*dp[1:])
C++代码
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
int main() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    unordered_map<char, int> mp;  // 记录字符出现次数
    unordered_map<char, long long> sm;  // 记录字符位置前缀和
    vector<long long> dp(n + 1, 0);  // 记录答案
    for (int i = 1; i <= n; i++) {
        char c = s[i - 1];
        int cnt = mp[c];  // 统计 s[i] 出现的次数
        long long res = 0;
        if (cnt != 0) {
            res = (i - 1) * cnt - sm[c];  // 计算所有 j 对 i 的贡献
        }
        dp[i] = dp[i - 1] + res;  // 累加贡献
        // 更新 mp 和 sm
        mp[c] = cnt + 1;
        sm[c] += i;
    }
    for (int i = 1; i <= n; i++) {
        cout << dp[i] << " ";
    }
    cout << endl;
    return 0;
}
Java代码
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        String s = sc.next();
        Map<Character, Integer> mp = new HashMap<>();  // 记录字符出现次数
        Map<Character, Long> sm = new HashMap<>();  // 记录字符位置前缀和
        long[] dp = new long[n + 1];  // 记录答案
        for (int i = 1; i <= n; i++) {
            char c = s.charAt(i - 1);
            int cnt = mp.getOrDefault(c, 0);  // 统计 s[i] 出现的次数
            long res = 0;
            if (cnt != 0) {
                res = (long) (i - 1) * cnt - sm.getOrDefault(c, 0L);  // 计算所有 j 对 i 的贡献
            }
            dp[i] = dp[i - 1] + res;  // 累加贡献
            // 更新 mp 和 sm
            mp.put(c, cnt + 1);
            sm.put(c, sm.getOrDefault(c, 0L) + i);
        }
        for (int i = 1; i <= n; i++) {
            System.out.print(dp[i] + " ");
        }
        System.out.println();
        sc.close();
    }
}
        题目内容
小美定义一个字符串的权值为:其长度为3的回文子序列数量。例如,"abca"的权值为2,因为包含 两个回文子序列"aba"和"aca"。
现在给定一个仅包含小写字母的字符串,小美希望你输出该字符串每个前缀的权值。
输入描述
第一行输入一个正整数n,代表字符串长度。
第二行输入一个长度为n的、仅包含小写字母的字符 串。 1≤n≤105
输出描述
输出n个整数,分别代表长度为1到n每个前缀的权值。
样例1
输入
6
aaaaba
输出
0 0 1 4 4 14
说明
对于"aaaaba",共有10个"aaa"子序列和4个"aba"子序列,因此权值是14。