3501. Maximize Active Section with Trade II
- String
- Binary Search
- Sparse Table
- Prefix Sum
- Greedy
TIME
O(n log n + q log n)
SPACE
O(n log n)
Intuition
Part I established the core accounting: on t = '1' + substring + '1', a trade around a 1-run B flanked by 0-runs A and C nets exactly A + C (you lose B, then regain it inside the merged block). So per query:
answer = total_ones(s) + bestGain(l, r)total_ones(s) is a constant, the characters outside [l, r] are untouched, and the ones inside are fully accounted for by the gain. What changes per query is bestGain(l, r) = the largest sum of two adjacent zero-runs inside the augmented substring.
The only subtlety versus part I is clipping: the first and last zero-runs touching [l, r] are cut off at the query boundary, while every run strictly inside keeps its full length.
Approach
Preprocessing:
- Collect all maximal zero-runs of
sas(start, end)with full lengths. - Build
P[i] = len[i] + len[i+1]the adjacent-pair sums, which never depend on a query. - Build a sparse table over
Pfor O(1) range-maximum queries.
Per query [l, r]:
- Binary-search the range of runs intersecting
[l, r]:a= first run withend >= l,b= last run withstart <= r. Ifa > b, no zero-runs → gain 0. - If
a == b, only one zero-run exists → no adjacent pair → gain 0. - Compute the clipped boundary lengths:
lenA = min(end_a, r) - max(start_a, l) + 1lenB = min(end_b, r) - max(start_b, l) + 1
- Take the max of:
- the leading pair
(a, a+1):lenA + (a+1 == b ? lenB : fullLen[a+1]) - the trailing pair
(b-1, b):(b-1 == a ? lenA : fullLen[b-1]) + lenB - the interior pairs: sparse-table range max of
Pover[a+1, b-2](only whena+1 <= b-2)
- the leading pair
answer = total_ones + gain.
Complexity
- Time: O(n log n) preprocessing + O(log n) per query for the binary searches (range max is O(1))
- Space: O(n log n), the sparse table
Edge Cases
- Query range with no zeros, or only one zero-run → gain 0 (no valid trade)
a == bmust return 0, not2 * lenAa single run has no adjacent partnerb == a + 1→ exactly one pair, both lengths clipped- Boundary runs partially outside
[l, r]→ must use clipped, not full, lengths - Answer counts ones across all of
s, not just the substring (untouched characters still count)
Code
from typing import List
from bisect import bisect_left, bisect_right
class Solution:
def maxActiveSectionsAfterTrade(self, s: str, queries: List[List[int]]) -> List[int]:
"""
3501. Maximize Active Section with Trade II
Time: O(n log n + q log n)
Space: O(n log n)
"""
n = len(s)
total_ones = s.count("1")
# Maximal zero-runs of s.
starts, ends, lengths = [], [], []
i = 0
while i < n:
if s[i] == "0":
j = i
while j < n and s[j] == "0":
j += 1
starts.append(i)
ends.append(j - 1)
lengths.append(j - i)
i = j
else:
i += 1
cnt = len(starts)
# P[i] = full adjacent-pair sum; sparse table for range max.
P = [lengths[i] + lengths[i + 1] for i in range(cnt - 1)]
m = len(P)
LOG = max(1, m.bit_length())
sparse = [P[:]] if m else [[]]
k = 1
while (1 << k) <= m:
prev = sparse[k - 1]
span = 1 << (k - 1)
sparse.append([max(prev[i], prev[i + span]) for i in range(m - (1 << k) + 1)])
k += 1
def range_max(lo: int, hi: int) -> int:
# max of P[lo..hi]; caller guarantees lo <= hi and valid bounds
k = (hi - lo + 1).bit_length() - 1
return max(sparse[k][lo], sparse[k][hi - (1 << k) + 1])
ans = []
for l, r in queries:
a = bisect_left(ends, l) # first run with end >= l
b = bisect_right(starts, r) - 1 # last run with start <= r
if a > b or a >= cnt or b < 0:
ans.append(total_ones)
continue
if a == b:
# single zero-run: no adjacent partner, no valid trade
ans.append(total_ones)
continue
len_a = min(ends[a], r) - max(starts[a], l) + 1
len_b = min(ends[b], r) - max(starts[b], l) + 1
# leading pair (a, a+1) and trailing pair (b-1, b)
gain = len_a + (len_b if a + 1 == b else lengths[a + 1])
gain = max(gain, (len_a if b - 1 == a else lengths[b - 1]) + len_b)
# fully-interior pairs
if a + 1 <= b - 2:
gain = max(gain, range_max(a + 1, b - 2))
ans.append(total_ones + gain)
return ansimport java.util.ArrayList;
import java.util.List;
/**
* 3501. Maximize Active Section with Trade II
* Time: O(n log n + q log n)
* Space: O(n log n)
*/
class Solution {
private int[][] sparse;
private int[] logTable;
public List<Integer> maxActiveSectionsAfterTrade(String s, int[][] queries) {
int n = s.length();
int totalOnes = 0;
for (int i = 0; i < n; i++) {
if (s.charAt(i) == '1') {
totalOnes++;
}
}
// Maximal zero-runs of s.
List<int[]> runs = new ArrayList<>(); // {start, end}
int i = 0;
while (i < n) {
if (s.charAt(i) == '0') {
int j = i;
while (j < n && s.charAt(j) == '0') {
j++;
}
runs.add(new int[]{i, j - 1});
i = j;
} else {
i++;
}
}
int cnt = runs.size();
int[] starts = new int[cnt], ends = new int[cnt], lengths = new int[cnt];
for (int k = 0; k < cnt; k++) {
starts[k] = runs.get(k)[0];
ends[k] = runs.get(k)[1];
lengths[k] = ends[k] - starts[k] + 1;
}
// P[i] = full adjacent-pair sum; sparse table for range max.
int m = Math.max(0, cnt - 1);
int[] P = new int[m];
for (int k = 0; k < m; k++) {
P[k] = lengths[k] + lengths[k + 1];
}
buildSparse(P, m);
List<Integer> ans = new ArrayList<>(queries.length);
for (int[] q : queries) {
int l = q[0], r = q[1];
int a = lowerBoundEnds(ends, cnt, l); // first run with end >= l
int b = upperBoundStarts(starts, cnt, r) - 1; // last run with start <= r
if (a > b || a >= cnt || b < 0 || a == b) {
ans.add(totalOnes);
continue;
}
int lenA = Math.min(ends[a], r) - Math.max(starts[a], l) + 1;
int lenB = Math.min(ends[b], r) - Math.max(starts[b], l) + 1;
// leading pair (a, a+1) and trailing pair (b-1, b)
int gain = lenA + (a + 1 == b ? lenB : lengths[a + 1]);
gain = Math.max(gain, (b - 1 == a ? lenA : lengths[b - 1]) + lenB);
// fully-interior pairs
if (a + 1 <= b - 2) {
gain = Math.max(gain, rangeMax(a + 1, b - 2));
}
ans.add(totalOnes + gain);
}
return ans;
}
private void buildSparse(int[] P, int m) {
logTable = new int[Math.max(2, m + 1)];
for (int i = 2; i <= m; i++) {
logTable[i] = logTable[i / 2] + 1;
}
int levels = (m > 0) ? logTable[m] + 1 : 1;
sparse = new int[levels][];
sparse[0] = P;
for (int k = 1; k < levels; k++) {
int len = m - (1 << k) + 1;
sparse[k] = new int[Math.max(0, len)];
int span = 1 << (k - 1);
for (int i = 0; i < len; i++) {
sparse[k][i] = Math.max(sparse[k - 1][i], sparse[k - 1][i + span]);
}
}
}
private int rangeMax(int lo, int hi) {
int k = logTable[hi - lo + 1];
return Math.max(sparse[k][lo], sparse[k][hi - (1 << k) + 1]);
}
/** First index with ends[idx] >= target. */
private int lowerBoundEnds(int[] ends, int cnt, int target) {
int lo = 0, hi = cnt;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (ends[mid] >= target) {
hi = mid;
} else {
lo = mid + 1;
}
}
return lo;
}
/** First index with starts[idx] > target. */
private int upperBoundStarts(int[] starts, int cnt, int target) {
int lo = 0, hi = cnt;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (starts[mid] > target) {
hi = mid;
} else {
lo = mid + 1;
}
}
return lo;
}
}