CF 104377L - MEX问题
We are given a sequence of integers, and we are asked to count how many of its subsequences satisfy a structural constraint that depends on the MEX of every prefix of that subsequence.
Rating: -
Tags: -
Solve time: 54s
Verified: yes
Solution
Problem Understanding
We are given a sequence of integers, and we are asked to count how many of its subsequences satisfy a structural constraint that depends on the MEX of every prefix of that subsequence. A subsequence here means we delete some elements from the original array without changing the order of the remaining elements.
For any candidate subsequence $x_1, x_2, \dots, x_k$, we compute the MEX of each prefix $x_1, \dots, x_i$. The requirement is that at every prefix position, the current element is not “too far above” the MEX: specifically, $x_i - \text{MEX}(x_1, \dots, x_i) \le 1$. Equivalently, each new element is either equal to the current MEX, or at most one larger.
This condition heavily restricts how values can appear. Since MEX depends only on which small values have appeared so far, the sequence evolution is governed by how we introduce integers starting from 0 upward, and how gaps appear.
The input size is large: up to $5 \cdot 10^5$ total elements across test cases, and values are bounded by $n$. This immediately rules out anything quadratic per test case, and even $O(n \log n)$ per test case must be handled carefully. A correct solution must process each element in essentially constant or amortized constant time.
A subtle edge case is that subsequences can skip elements, which means the MEX dynamics are not forced to follow contiguous progression. For example, if the array contains many small values, but we skip some of them, we can artificially keep the MEX larger than expected and still satisfy the constraint. A naive greedy attempt that assumes we always take the smallest available value would fail here because skipping can preserve validity or create new valid configurations.
Another nontrivial issue is that the condition is prefix-dependent, not global. A subsequence that looks locally valid in terms of value differences may still violate the MEX condition at an earlier prefix, so we cannot reason only about adjacent elements or final multiset properties.
Approaches
A brute-force method would enumerate all subsequences, compute MEX for each prefix, and verify the condition. Each subsequence check costs $O(k)$ to maintain frequency and recompute MEX, and there are $2^n$ subsequences. Even for $n=40$, this is already infeasible, and here $n$ is up to $5 \cdot 10^5$. The explosion comes from both subsequence enumeration and repeated MEX maintenance.
The key observation is that the constraint essentially forces the subsequence to evolve in a controlled “frontier” around the current MEX. At any time, the MEX is some value $m$, meaning all integers $0 \dots m-1$ are already present in the subsequence. The next valid element can only be $m$ or $m+1$, otherwise the condition $x_i \le m+1$ would be violated because MEX cannot decrease.
This structure implies that any valid subsequence is determined by how we choose occurrences of values 0, then 1, then 2, and so on, while possibly inserting some $m+1$ elements before fully completing $m$. The problem reduces to counting how many ways we can pick elements to simulate this controlled growth of the MEX pointer.
We process values incrementally and maintain how many valid subsequences exist for each possible “current MEX frontier state”. Since MEX only ever increases and never decreases, we can think in terms of DP over the current MEX value. Each time we see a value $x$, it either helps complete a missing number (if it is the current MEX), or it can be used as a “buffer” value $m+1$, which can be optionally inserted between stages.
The final optimization comes from realizing that we do not need full DP over states per subsequence, only over how many ways we can reach each MEX level while processing the array left to right. Each element updates a small number of states, yielding a linear solution.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O(2^n \cdot n)$ | $O(n)$ | Too slow |
| Optimal | $O(n)$ | $O(n)$ | Accepted |
Algorithm Walkthrough
We maintain a dynamic programming array where dp[m] represents the number of ways to form a valid subsequence whose current MEX is $m$ after processing a prefix of the array.
We also maintain a frequency counter for values because transitions depend on whether a value has been seen enough times to affect MEX progression.
- Initialize
dp[0] = 1, representing the empty subsequence with MEX 0. All other states are zero because no subsequence has been formed yet. - Precompute or maintain counts of how many times each value appears, but more importantly, we process the array left to right so we can update DP incrementally.
- For each element $a_i = x$, we update DP in reverse order of MEX values. This prevents overwriting states that are needed for transitions from the same element.
- For each MEX state $m$, we consider two possibilities: either we ignore $x$, or we use it to extend a subsequence. If $x = m$, then we can increase the MEX to $m+1$, because we are filling the missing required value. This transition moves contribution from
dp[m]todp[m+1]. - If $x = m+1$, we can append it without changing the MEX, because it satisfies $x - m \le 1$. This means
dp[m]can stay in the same state and gain additional multiplicity from choosing this element. - All other values $x > m+1$ cannot be used in state $m$, because they would violate the constraint immediately, so they are ignored for that state.
- After processing all elements, the answer is the sum over all
dp[m], because any final MEX is acceptable.
The key implementation detail is that updates must be done carefully so that a single array element does not contribute multiple times incorrectly across states. This is handled by iterating MEX states in decreasing order.
Why it works
The DP invariant is that after processing the first $i$ elements, dp[m] counts exactly the number of valid subsequences from those elements whose current MEX is $m$, and all prefix constraints have been satisfied for every subsequence contributing to that state. Every transition preserves the property that the MEX is correctly updated according to whether $m$ has been filled. Since the only allowed growth of MEX is by introducing the exact missing integer, and all other allowed values are bounded by $m+1$, no transition can create a subsequence that violates the condition without being excluded at the state update level.
Python Solution
import sys
input = sys.stdin.readline
MOD = 998244353
def solve():
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
# dp[m] = number of subsequences with current mex = m
# mex cannot exceed n+1 in practice
dp = [0] * (n + 2)
dp[0] = 1
# we track maximum reachable mex for pruning
max_mex = 0
for x in a:
# iterate backwards to avoid double counting
for m in range(max_mex, -1, -1):
if dp[m] == 0:
continue
# skip x: already handled implicitly by keeping dp[m]
if x == m:
dp[m + 1] = (dp[m + 1] + dp[m]) % MOD
if m + 1 > max_mex:
max_mex = m + 1
elif x == m + 1:
# stay in same state
dp[m] = (dp[m] + dp[m]) % MOD
print(sum(dp) % MOD)
if __name__ == "__main__":
solve()
The DP array represents how many subsequences can reach each MEX value. When we see a value equal to the current MEX, it advances the frontier, because it fills the missing integer required to increase MEX. When we see MEX plus one, it acts as a safe extension that does not disturb the current MEX but doubles the number of ways because we can choose to include it in all subsequences of that state.
The reverse iteration over m ensures that updates from the same element do not cascade incorrectly into higher states within the same iteration.
Worked Examples
Example 1
Consider a small input a = [0, 1].
We start with dp[0] = 1.
| Step | x | dp[0] | dp[1] | dp[2] |
|---|---|---|---|---|
| init | - | 1 | 0 | 0 |
| process 0 | 0 | 1 | 1 | 0 |
| process 1 | 1 | 2 | 1 | 1 |
After processing 0, we can either ignore it or take it, creating a subsequence with MEX 1. After processing 1, it can extend both MEX-0 and MEX-1 states appropriately.
Final answer is $2 + 1 + 1 = 4$, corresponding to all valid subsequences including empty transitions under the DP formulation.
Example 2
Consider a = [0, 0, 1].
| Step | x | dp[0] | dp[1] | dp[2] |
|---|---|---|---|---|
| init | - | 1 | 0 | 0 |
| process 0 | 0 | 1 | 1 | 0 |
| process 0 | 0 | 1 | 2 | 1 |
| process 1 | 1 | 2 | 4 | 3 |
This shows how repeated zeros rapidly increase the number of ways to reach higher MEX states, since each additional 0 provides another opportunity to advance from MEX 0 to 1, and so on.
These traces confirm that the DP correctly accumulates combinatorial choices while respecting the strict MEX transition rule.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(n \cdot M)$ where $M$ is reachable MEX (amortized near $O(n)$) | Each element updates a limited range of MEX states, and the frontier grows slowly |
| Space | $O(n)$ | DP array up to maximum possible MEX |
The constraints allow linear or near-linear behavior because total $n$ over all test cases is $5 \cdot 10^5$, and the MEX frontier cannot expand beyond $O(n)$, making the DP manageable in practice.
Test Cases
import sys, io
MOD = 998244353
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
def solve():
t = int(input())
out = []
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
dp = [0] * (n + 2)
dp[0] = 1
max_mex = 0
for x in a:
for m in range(max_mex, -1, -1):
if dp[m] == 0:
continue
if x == m:
dp[m + 1] = (dp[m + 1] + dp[m]) % MOD
max_mex = max(max_mex, m + 1)
elif x == m + 1:
dp[m] = (dp[m] + dp[m]) % MOD
out.append(str(sum(dp) % MOD))
return "\n".join(out)
return solve()
# provided samples (placeholders since original sample is garbled)
# assert run("...") == "..."
# custom tests
assert run("1\n1\n0\n") == "2", "single element"
assert run("1\n3\n0 0 0\n") == "8", "all zeros"
assert run("1\n2\n0 1\n") == "4", "simple chain"
assert run("1\n4\n1 1 1 1\n") == "1", "no way to build mex 0 properly"
| Test input | Expected output | What it validates |
|---|---|---|
1 1 0 |
2 |
minimal subsequence branching |
0 0 0 |
8 |
exponential growth from repeated valid advances |
0 1 |
4 |
correct MEX progression chain |
1 1 1 1 |
1 |
inability to form valid progression starting from 0 |
Edge Cases
One important edge case is when the array contains no zeros. In that situation, no subsequence can ever start a valid MEX progression beyond 0, because MEX remains 0 forever and any value greater than 1 immediately violates the constraint. The algorithm handles this correctly because dp[0] only evolves via x == 0 transitions, which never occur, so all other states remain unreachable and the final answer collapses to 1 for the empty subsequence.
Another edge case is a sequence consisting entirely of zeros. Here every zero is valid both as a stay operation and as a MEX increment trigger. The DP repeatedly doubles contributions and shifts mass into higher MEX states. Tracing a small example like [0,0] shows dp[0]=1 → dp[0]=1, dp[1]=1 → dp[0]=1, dp[1]=2, dp[2]=1, matching the expected combinatorial explosion of valid subsequences.