Introduction
We are given a string containing only the three characters A, B, and C, and need to count its nonempty substrings satisfying:
The numbers of occurrences of
A,B, andCin the substring are pairwise distinct.
Let the three character counts in a substring be . The validity condition is that are pairwise distinct:
Equivalently:
The string length satisfies:
So we cannot enumerate all substrings directly.
1. The Problem with Brute Force
The most direct idea is to enumerate every substring , then use prefix sums to obtain its counts of A, B, and C in .
For example, precompute the following, where preA[i], preB[i], and preC[i] count the respective characters among the first i characters:
1 | preA[i] = 前 i 个字符中 A 的出现次数 |
The three counts in substring are then:
We can then check whether are pairwise distinct.
However, the number of substrings is:
When , this is on the order of , so enumerating substrings in is infeasible.
The key question is not how to find one substring’s character counts quickly, but:
How can we count substrings satisfying a particular relationship between their counts without enumerating the substrings?
2. Count the Invalid Substrings Instead
We need to count substrings satisfying:
Counting pairwise-distinct frequencies directly is awkward, so consider the complement.
An invalid substring satisfies at least one condition: , or , or :
We can therefore start by counting:
- Substrings with equal numbers of
AandB - Substrings with equal numbers of
BandC - Substrings with equal numbers of
CandA
Then subtract the number of invalid substrings from the total.
But these three sets overlap. If a substring satisfies:
it belongs to all three sets:
In other words, it is counted three times.
As an invalid substring, however, it should be counted only once.
We will therefore need inclusion–exclusion to handle this overlap.
3. From to Equal Prefix Differences
First consider just one condition: a substring contains equal numbers of A and B.
Define the prefix difference:
If substring contains equal numbers of A and B, then:
Rearranging gives:
That is:
Therefore:
Substring contains equal numbers of
AandBif and only if the difference is the same at prefix positions and .
This changes the problem from enumerating substrings to counting how often the same prefix state occurs.
4. Counting Prefix States
If we scan the string from left to right while maintaining the current prefix difference , then at each prefix position we only need to know:
How many earlier prefix positions also had a difference equal to the current ?
Suppose the current difference is , and it has already occurred mp[d] times. Each of those earlier positions can pair with the current prefix position to form a substring with equal numbers of A and B.
The new contribution is therefore:
1 | ans += mp[d]; |
Rather than enumerating every left endpoint, this uses mp[d] to count all eligible left endpoints at once.
Similarly:
- To count , use the difference
- To count , use the difference
- To count , use the difference
The corresponding code is:
1 | int d1 = preA - preB; |
Here, tot2 records the sum of the sizes of the three sets:
5. Why We Also Need to Count
If we calculate only:
we count some substrings more than once.
A substring satisfying:
also satisfies all of the following:
It is therefore counted once in each of , , and , for a total of three times.
It should be counted only once as an invalid substring, so there are two extra counts.
Let be the number of substrings satisfying . The number of invalid substrings is then:
The final answer is therefore:
Or:
In code:
1 | cout << tot - tot2 + tot3 * 2; |
Here:
tot: the number of all nonempty substringstot2:tot3:
6. Counting with a Two-Dimensional State
Next, we need to count substrings satisfying:
This can be split into two conditions:
For each prefix state, we can therefore maintain two differences at once:
For substring , if:
the substring between those prefix positions satisfies both:
and:
Hence:
We can therefore count using a two-dimensional state:
1 | tot3 += mpABC[getKey(d1, d2)]++; |
The differences used here are:
1 | d1 = preA - preB; |
Whenever two prefix positions have exactly the same (d1,d2), the substring between them satisfies A=B=C.
7. Encoding a Two-Dimensional State as long long
In C++, unordered_map<pair<int,int>, long long> cannot be used directly with its default settings, because the standard library provides no default hash function for pair<int,int>.
We can instead encode the two-dimensional state manually as a long long.
The code is:
1 | constexpr ll offset = 200005, base = 500005; |
The prefix differences stay within:
Since , adding offset makes each difference nonnegative.
base must be larger than the maximum possible value of the second coordinate, so that distinct pairs cannot be encoded as the same key.
One way to understand this is:
Encode the two-dimensional coordinates as one integer, much like a positional numeral system.
That is:
As long as:
the encoding is unique.
8. Initialization
We must include the empty prefix, too.
For a substring starting at the st character, the corresponding prefix endpoints are and :
Initially, each zero-difference state must therefore have one occurrence:
1 | void init() { |
Here:
is the number of all nonempty substrings.
From the prefix perspective, there are prefix positions. Choosing any two distinct prefix positions determines a nonempty substring, so we can also write:
9. The Main Loop
While scanning the string, we only need the counts of A, B, and C in the current prefix; there is no need to store entire prefix arrays.
1 | for (int i = 0; i < n; i++) { |
After reading each character, update the current prefix counts, then count the new contributions whose right endpoint is the current position.
The cnt function is:
1 | void cnt() { |
The central idea is:
Each earlier occurrence of the current state adds one valid pair ending at the current position.
10. Complete Code and Submission Record
Expand/collapse the final accepted code
1 |
|
The submission record below shows that both the time and space performance are quite good.
And here are some notes I scribbled while working on the problem :)
11. Complexity Analysis
We scan the string once, performing a constant number of hash-table lookups and updates at each position, so the time complexity is:
At most prefix states are stored in the hash tables, so the space complexity is:
In this problem:
so this complexity is sufficient to pass.
Closing Thoughts
The key to this problem is not prefix sums themselves, but turning relationships between character counts in a substring into equality between two prefix states.
A condition such as can be counted using one-dimensional prefix differences. A condition such as , which requires two equalities to hold simultaneously, needs a two-dimensional prefix state.
Finally, inclusion–exclusion handles the repeated counts among the three sets of invalid substrings.
The overall approach is:
- Use all substrings as the universal set
- Count the invalid substrings in which at least two character counts are equal
- Use prefix differences to count the three one-dimensional conditions
- Use two-dimensional differences to count the overlap where all three counts are equal
- Apply inclusion–exclusion to obtain the final answer
The useful lesson from problems like this is:
Prefix sums do more than answer interval-sum queries quickly. Equality between prefix states can also count intervals satisfying particular relationships.