Unbalanced ABC Substrings

Unbalanced ABC Substrings

(容斥原理,前缀和,哈希映射,自定义哈希函数,抽屉原理)

问题描述

给定一个长度为 (N) 的字符串 (S),仅由大写字母 A、B、C 组成。
求有多少个非空子串,满足子串中 A、B、C 的出现次数两两互不相同。

子串定义:从原串中删除开头若干个字符和结尾若干个字符后得到的连续片段。
两个子串若位置不同,即使内容相同也分别计数。


输入格式

  • 第一行:一个整数 (N),表示字符串长度。
  • 第二行:一个长度为 (N) 的字符串 (S),只包含字符 A、B、C。

输出格式

输出一个整数,表示满足条件的非空子串数量。

约束条件

  • (1 \le N \le 2\times 10^5)
  • (|S| = N)
  • (S) 只含 A、B、C

解法思路

直接枚举所有子串的复杂度为 (O(N^2)),无法满足 (N \le 2\times 10^5) 的要求。
本题的关键在于:

  1. 容斥原理:将“三个数互不相同”转化为对“相等事件”的计数。
  2. 前缀状态哈希:将子串条件转化为前缀位置之间的差值相等关系,从而用哈希表统计频次,在 (O(N)) 时间内求出满足各种相等条件的子串数量。

1. 容斥原理转化

设任意子串中三个字符的个数分别为 ((a,b,c))。
题目要求:(a \ne b,\ b \ne c,\ c \ne a),等价于以下三个事件都不发生:

  • (E_{AB}: a = b)
  • (E_{BC}: b = c)
  • (E_{CA}: c = a)

根据容斥原理,满足条件的子串数为:

[
\begin{aligned}
\text{答案} &= \text{总子串数} - f_{AB} - f_{BC} - f_{CA} \
&\quad + f_{AB \cap BC} + f_{BC \cap CA} + f_{CA \cap AB} - f_{AB \cap BC \cap CA}
\end{aligned}
]

其中 (f_X) 表示满足事件 (X) 的子串个数。
注意到任意两个相等事件同时发生(例如 (a=b) 且 (a=c))等价于 (a=b=c),因此:

[
f_{AB \cap BC} = f_{BC \cap CA} = f_{CA \cap AB} = f_{ABC}
]
且
[
f_{AB \cap BC \cap CA} = f_{ABC}
]

代入得:

[
\text{答案} = \text{总子串数} - f_{AB} - f_{BC} - f_{CA} + 3f_{ABC} - f_{ABC}
= \text{总子串数} - f_{AB} - f_{BC} - f_{CA} + 2f_{ABC}
]

其中:

  • (f_{AB}):满足 (a=b) 的子串数
  • (f_{BC}):满足 (b=c) 的子串数
  • (f_{CA}):满足 (c=a) 的子串数
  • (f_{ABC}):满足 (a=b=c) 的子串数

总子串数 (\text{total} = \frac{N(N+1)}{2})。

因此,问题简化为高效计算这四个函数值。


2. 利用前缀和与哈希表计算

定义前缀计数:

  • (A_i):前 (i) 个字符中 A 的个数((0 \le i \le N))
  • (B_i)、(C_i) 同理

对于子串 ((l+1, r)),其中 (0 \le l < r \le N),该子串内各字符数量为:

[
a = A_r - A_l,\quad b = B_r - B_l,\quad c = C_r - C_l
]

2.1 计算 (f_{AB})((a=b))

[
A_r - A_l = B_r - B_l \iff A_l - B_l = A_r - B_r
]

因此,每个前缀位置 (i) 对应一个差值 (d_i = A_i - B_i)。
对于任意一对位置 ((l, r)),若 (d_l = d_r),则它们构成的子串满足 (a=b)。
统计每个差值出现的次数,若某差值出现 (cnt) 次,则从中任选两个不同位置配对,贡献为 (\binom{cnt}{2})。

所以:
[
f_{AB} = \sum_{d} \binom{\text{count}(d)}{2}
]

2.2 计算 (f_{BC}) 与 (f_{CA})

同理:

  • (f_{BC}):使用差值 (B_i - C_i)
  • (f_{CA}):使用差值 (C_i - A_i)(或用 (A_i - C_i),符号相反不影响计数)

2.3 计算 (f_{ABC})((a=b=c))

需同时满足:
[
A_l - B_l = A_r - B_r \quad\text{且}\quad A_l - C_l = A_r - C_r
]

因此,每个前缀位置 (i) 对应一个二维点 ((A_i - B_i,\ A_i - C_i))。
当两个位置的点相同时,对应的子串满足 (a=b=c)。
统计每个二维点出现的次数,累加 (\binom{cnt}{2}) 即得 (f_{ABC})。


#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct pair_hash {
    size_t operator()(const pair<int, int>& p) const {
        return hash<int>{}(p.first) ^ (hash<int>{}(p.second) << 1);
    }
};

void solve() {
    int n; cin >> n;
    string s; cin >> s;
    int a = 0, b = 0, c = 0;
    ll fab = 0, fbc = 0, fca = 0, fabc = 0;
    unordered_map<int, int> ab, bc, ca;
    unordered_map<pair<int, int>, ll, pair_hash> abc;
    ab[0] = 1; bc[0] = 1, ca[0] = 1; abc[{0, 0}] = 1;
    

    for(int i = 1; i <= s.size(); i ++) {
        if(s[i - 1] == 'A') a ++;
        if(s[i - 1] == 'B') b ++;
        if(s[i - 1] == 'C') c ++;
        
        int dAB = a - b, dAC = a - c, dBC = b - c;
        auto keyABC = make_pair(dAB, dAC);

        fab += ab[dAB];
        fbc += bc[dBC];
        fca += ca[dAC];
        fabc += abc[keyABC];

        ab[dAB] ++;
        ca[dAC] ++;
        bc[dBC] ++;
        abc[keyABC] ++;
    }
    ll tol = 1LL * (n + 1) * n / 2;
    cout << tol - fab - fbc - fca + 2LL * fabc << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int t = 1; //cin >> t;
    while(t --) solve();
    return 0;
}
posted @ 2026-09-25 09:33  thenrify  阅读(7)  评论(0)    收藏  举报