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) 的要求。
本题的关键在于:
- 容斥原理:将“三个数互不相同”转化为对“相等事件”的计数。
- 前缀状态哈希:将子串条件转化为前缀位置之间的差值相等关系,从而用哈希表统计频次,在 (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;
}

浙公网安备 33010602011771号