queries each ask “after how many of the operations does my condition first become true?”, where the condition is monotone in time. Binary searching each query separately rebuilds the structure times. Doing all the searches together sweeps the operations only times.

Iterative form

vector<int> lo(q, 1), hi(q, m + 1);                 // answer in [lo, hi]; m + 1 = never
for (bool changed = true; changed; ) {
    changed = false;
    vector<vector<int>> at(m + 2);
    for (int i = 0; i < q; i++) if (lo[i] < hi[i]) at[(lo[i] + hi[i]) / 2].push_back(i), changed = true;
    reset();                                         // fresh structure (or rollback to empty)
    for (int t = 1; t <= m; t++) {
        apply(t);
        for (int i : at[t]) {
            if (check(i)) hi[i] = t; else lo[i] = t + 1;
        }
    }
}

The cost is rounds, each costing .

The structure only needs to be built incrementally (insert-only). That’s the main advantage over offline-deletion techniques: a plain DSU or BIT suffices.

Recurse on the answer interval together with the queries whose answer lies there and the operations with time in . Apply the operations in , split the queries by check, undo, and recurse left and right. The total work is applications, and there’s no global reset per round. Used for:

“Fracturing search” is the name this exact recursion goes by in some references (e.g. USACO Guide); it’s the same algorithm, just framed slightly more generally than “parallel binary search” strictly defined. The iterative form above needs every query’s check to be the same monotone timeline (one shared direction of “becomes true”); the recursive form only needs each query’s own predicate to be monotone within the interval it’s currently routed to — nothing requires all queries to share one global answer axis or even the same comparison direction, since each query is independently split left/right by its own check at its own recursion node. That’s why it comes up for problems with per-query thresholds that aren’t literally “after which timestep,” as long as each one is checkable against the same incrementally-built structure.

  • range -th smallest with point updates: the answer is a value; operations are “insert/delete value at position”; check is a BIT range count compared with . , offline, no persistence.
  • first time each region reaches a quota (POI “Meteors”): the operations are range adds on a circular array, the queries ask when an owner’s total reaches its demand.

Examples

Queryapplycheck
first edge index after which are connectedDSU unionsame component
first time a component containing has size DSU unionsize
first time the graph restricted to edges so far becomes non-bipartite, per query vertex setDSU with parityparity conflict
-th smallest in (static)insert positions of values in sorted order into a BITcount in
first day a country’s collected total meets its demandrange add (BIT)sum over its stations, capped to avoid overflow
minimum bottleneck between and edges sorted by weight, DSUconnected
minimum capacity for all flows to succeedadd an edgemax-flow value
first round in which a player is eliminatedapply a roundevaluate a condition

For “min bottleneck between and ” there’s also an online alternative: the Kruskal reconstruction tree, where the answer is the weight at (Kruskal Reconstruction Tree).

Parallel binary search vs. the alternatives

TechniqueHandlesCostStructure needed
Parallel binary search”when does the answer flip”, many queries rebuildsincrementally buildable (add-only)
Segment tree on timequeries at arbitrary times, add and remove per operationrollback-able
CDQmulti-dimensional dominanceBIT
Persistent structuresonline historical queries per querypersistent
Independent binary searches per queryfew queriesrebuildable

The distinguishing feature: parallel binary search needs only an incrementally buildable structure, not a rollback-able or persistent one — a plain DSU or BIT suffices where offline dynamic connectivity would need rollback.

Requirements and pitfalls

  • The predicate must be monotone in time (once true, always true). Check it before coding.
  • Reset fully between rounds; leftover state is the usual bug. With a rollback DSU you can undo to empty instead.
  • Use the sentinel for “never” and test it at the end.
  • Overflow in accumulated checks (sums of demands): cap at the threshold.
  • The rounds are independent, so this parallelises trivially across threads if that is ever useful.