952 字
5 分钟
abc455

E - Unbalanced ABC Substrings#

题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_e

思路#

暴力做法#

首先考虑暴力做法,我们枚举每一个子串可能的左端点并扩展右端点,在过程中动态维护A,B,CA,B,C的出现次数,时间复杂度O(n2)O(n^2)

评测结果:https://atcoder.jp/contests/abc455/submissions/75267993

AC 15,TLE 21

Code#

// Problem: AT ABC455 E
// Contest: AtCoder - Sky Inc, Programming Contest 2026 (AtCoder Beginner Contest 455)
// URL: https://atcoder.jp/contests/abc455/tasks/abc455_e
// Time: 2026-04-25 20:00:07
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
int n;
string s;
ll res;
bool check(int &a, int &b, int &c)
{
return (a != b && b != c && a != c);
}
int main()
{
cin >> n >> s;
for (int i = 0; i < n; i++)
{
int a = 0, b = 0, c = 0;
for (int j = i; j < n; j++)
{
if (s[j] == 'A')
a++;
if (s[j] == 'B')
b++;
if (s[j] == 'C')
c++;
if (check(a, b, c))
res++;
}
}
cout << res << endl;
return 0;
}

正解#

我们发现正着算“出现次数互不相同的子串”比较困难,我们考虑统计这个集合的补集,也就是有多少个子串中,至少有两个字符的出现次数相同

我们假设:

  • 集合 SABS_{AB}:子串中 A 的数量 = B 的数量
  • 集合 SBCS_{BC}:子串中 B 的数量 = C 的数量
  • 集合 SCAS_{CA}:子串中 C 的数量 = A 的数量

则我们要求的答案为Ans=USABSBCSCAAns = U - |S_{AB} \cup S_{BC} \cup S_{CA}|

根据容斥原理,我们可以展开上述公式如下:

SABSBCSCA=SAB+SBC+SCASABSBCSBCSCASCASAB+SABSBCSCA|S_{AB} \cup S_{BC} \cup S_{CA}| = |S_{AB}| + |S_{BC}| + |S_{CA}| - |S_{AB} \cap S_{BC}| - |S_{BC} \cap S_{CA}| - |S_{CA} \cap S_{AB}| + |S_{AB} \cap S_{BC} \cap S_{CA}|

这里有一个关键的逻辑:如果 A = BB = C,那么自然有A = B = C,假设A,B,CA,B,C全相等的子串数有NABCN_{ABC}个,化简上述公式,可以得到 Ans=TotalSABSBCSCA+2NABCAns = Total - |S_{AB}| - |S_{BC}| - |S_{CA}| + 2 \cdot N_{ABC}

如何快速求出 SAB|S_{AB}|(即 AB 数量相等的子串)呢?我们可以使用前缀和的差值

遍历字符串 SS,记录到当前位置为止 ABC 的出现次数 cA,cB,cCc_A, c_B, c_C

对于任意一个子串 S[lr]S[l \dots r],如果它里面 AB 的数量相等,意味着:

cA[r]cA[l1]=cB[r]cB[l1]c_A[r] - c_A[l-1] = c_B[r] - c_B[l-1]

移项可得:

cA[r]cB[r]=cA[l1]cB[l1]c_A[r] - c_B[r] = c_A[l-1] - c_B[l-1]

结论:只要两个位置的前缀和差值 (cAcB)(c_A - c_B) 相等,这两个位置之间的子串中 AB 的数量就相等。

同理:

  • 记录 (cBcC)(c_B - c_C) 的频率来求 SBC|S_{BC}|
  • 记录 (cCcA)(c_C - c_A) 的频率来求 SCA|S_{CA}|
  • 记录 (cAcB,cBcC)(c_A - c_B, c_B - c_C) 的频率来求 NABCN_{ABC}

Code#

// Problem: AT ABC455 E
// Contest: AtCoder - Sky Inc, Programming Contest 2026 (AtCoder Beginner Contest 455)
// URL: https://atcoder.jp/contests/abc455/tasks/abc455_e
// Time: 2026-04-25 20:00:07
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
typedef pair<long long, long long> pll;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
int n;
string s;
ll res;
int main()
{
cin >> n >> s;
ll tot = 1LL * n * (n + 1) / 2;
map<ll, ll> fab, fbc, fca;
map<pll, ll> fabc;
ll a = 0, b = 0, c = 0;
fab[0] = 1;
fbc[0] = 1;
fca[0] = 1;
fabc[{0, 0}] = 1;
for (auto ch: s)
{
if (ch == 'A')
a++;
else if (ch == 'B')
b++;
else if (ch == 'C')
c++;
ll dab = a - b;
ll dbc = b - c;
ll dca = c - a;
fab[dab]++;
fbc[dbc]++;
fca[dca]++;
fabc[{dab, dbc}]++;
}
ll nab = 0, nbc = 0, nca = 0, nabc = 0;
for (auto const &[k, v]: fab)
nab += v * (v - 1) / 2;
for (auto const &[k, v]: fbc)
nbc += v * (v - 1) / 2;
for (auto const &[k, v]: fca)
nca += v * (v - 1) / 2;
for (auto const &[k, v]: fabc)
nabc += v * (v - 1) / 2;
res = tot - nab - nbc - nca + 2 * nabc;
cout << res << endl;
return 0;
}