Skip to content

Optimization -> Decision: The Binary Search Reduction

Quick Revisit

  • REACH FOR THIS WHEN: you need to optimize a value and the decision version ("can we achieve X?") is easier
  • KEY DECISION: is the feasibility function monotonic in the answer?
  • QUICK RULE: "minimize the maximum" or "maximize the minimum" -- reach for binary search on the answer first
  • SEE ALSO: Graph Modeling, DP State Design

Every "find the best X" problem might be a "can we achieve X?" problem in disguise.

Prerequisites: Binary Search

The Fundamental Idea

You want to minimize some value. That's hard -- you'd need to explore all possibilities and prove one is best.

But what if someone gave you a candidate answer and asked: "is this achievable?" That's often a completely different (easier) problem. And if you can answer that yes/no question efficiently, binary search finds the optimum.

The template:

lo, hi = bounds on the answer
while lo < hi:
    mid = (lo + hi) / 2
    if feasible(mid):
        hi = mid       // mid works, try smaller
    else:
        lo = mid + 1   // mid doesn't work, need bigger
return lo

The insight: you're converting an optimization problem into a decision problem, then solving the decision problem O(log(range)) times.

Read this twice: binary search is not the solution. The feasibility check is the solution, repeated logarithmically. If your feasible(mid) is hand-wavy or wrong, no amount of binary-search scaffolding will save you. Most "I implemented binary search and it gave the wrong answer" bugs are decision-version bugs.

Five Reductions in the Wild

1. "Minimum time, splitting an ordered sequence" -> "Can we finish in time T?"

Setup. Given n jobs with durations in a fixed order, partition them into k contiguous groups (workers receive consecutive jobs). Minimize the maximum group sum (the makespan).

Decision version. Given time budget T: walk left to right, summing into the current worker. When adding the next job would exceed T, start a new worker. Did you use k workers?

Binary search on T between max(ai) and ai.

Caveat. This greedy works only because the partition is into contiguous groups in the given order. Arbitrary scheduling on identical workers (any job to any worker) is P||Cmax — NP-hard in general. If the problem allows reordering, this decision check is wrong; for that flavor, see Factory Machines (problem 4 below), where the greedy structure is different.

2. "Maximum minimum distance" -> "Can all distances be >= D?"

Setup. Place k objects among n positions. Maximize the minimum pairwise distance.

Decision version. Given minimum distance D: greedily place objects. Put the first object at position 1. For each subsequent object, pick the leftmost position that's D away from the last placed object. Did you place all k?

Binary search on D. This is the classic "Aggressive Cows" problem.

cpp
bool canPlace(vector<int>& pos, int k, int minDist) {
    int placed = 1, last = pos[0];
    for (int i = 1; i < pos.size(); i++) {
        if (pos[i] - last >= minDist) {
            placed++;
            last = pos[i];
        }
    }
    return placed >= k;
}

3. "Minimum number of groups" -> "Can we do it with K groups?"

Setup. Partition items into groups subject to constraints (e.g., max sum per group). Minimize groups.

Decision version. Given K groups: try a greedy or DP check to see if K suffices. Binary search on K.

Often the decision version reveals that the answer is monotonic: if K groups work, K+1 definitely work too.

4. "Find the k-th smallest value" -> "How many values are <= X?"

Setup. You have an implicit set of values (e.g., products aibj for all pairs). Find the k-th smallest.

Decision version. Given X: count how many values are X. Binary search on X to find the smallest X where the count k.

This is how you solve "k-th smallest element in a sorted matrix" in O(nlog(range)) instead of O(n2logn).

5. Parallel Binary Search -- Many Queries at Once

Skip on first pass. This is materially harder than the rest of the page; come back after the simpler reductions feel automatic. The full treatment lives in parallel binary search.

Setup. You have q queries, each asking "what's the first time event ei becomes true?" and a sequence of events applied over time.

The reduction. Instead of binary searching independently for each query (O(qlogTcheck)), do them all in parallel:

  1. Each query maintains its own [lo, hi] range.
  2. For each round: group queries by their mid value. Simulate events up to each mid, answer all relevant queries, update their ranges.
  3. Repeat for O(logT) rounds.

Total: O((simulation cost)logT) regardless of q.

When Binary Search Fails

Binary search on the answer requires monotonicity: if the answer X is feasible, then all values "beyond" X (larger or smaller, depending on direction) must also be feasible.

It breaks when:

  • The feasibility function oscillates: feasible at 5, infeasible at 6, feasible at 7.
  • The problem has multiple local optima (not unimodal). For unimodal functions, consider ternary search instead.
  • The decision problem itself is as hard as the optimization. (Binary search only helps when the decision version is easier.)

Recognizing the Pattern

Ask yourself two questions:

  1. "If someone told me the answer was X, could I verify it (or check feasibility) efficiently?" If yes, and the feasibility is monotonic in X, binary search works.

  2. "Is the answer space continuous or integer?" Integer -> standard binary search. Continuous -> run for a fixed number of iterations (e.g., 100) instead of while lo < hi.

cpp
// Continuous binary search (floating point answer)
double lo = 0, hi = 1e18;
for (int iter = 0; iter < 100; iter++) {
    double mid = (lo + hi) / 2;
    if (feasible(mid)) hi = mid;
    else lo = mid;
}
// lo ~= hi ~= answer

Recognition Cues

If you see in the problem...Binary search on...Decision version check
"Minimize the maximum"The maximum valueCan all values be <= X? (greedy)
"Maximize the minimum"The minimum valueCan all values be >= X? (greedy)
"Minimum time to finish"Time TCan we finish in time T? (greedy assign)
"K-th smallest value" (implicit set)Value XHow many values <= X? (counting)
"Minimum number of groups"Group count KCan we do it in K groups? (greedy/DP)
"Maximum length of subarray with property"Length LDoes a subarray of length L exist? (sliding window)

When NOT to binary search:

SituationWhy it failsUse instead
Feasibility oscillates (true, false, true)Monotonicity violatedLinear scan or DP
Multiple local optimaUnimodal but not monotonicTernary search
Decision version is as hard as optimizationNo simplification gainedDirect optimization

Common Mistakes in Choosing

  • Assuming monotonicity without checking. Binary search requires: if answer X is feasible, then all values beyond X are also feasible (or all below, depending on direction). Test on small cases.
  • Off-by-one in bounds. The classic bug: lo = 0, hi = n vs lo = 1, hi = n+1. Get the invariant right: "lo is always feasible" or "hi is always infeasible."
  • Using binary search when the decision version is equally hard. Binary search only helps when "can we achieve X?" is strictly easier than "find the best X." If both require the same DP, skip the binary search.
  • Forgetting the continuous case. For floating-point answers, don't use while (lo < hi) -- use a fixed iteration count (e.g., 100 iterations) to avoid infinite loops from floating-point imprecision.
  • Not tightening bounds. The search range [lo, hi] should be as tight as possible. For "minimum time," lo = max(single job) and hi = sum(all jobs), not lo = 0 and hi = 10^18.

The Power of the Reduction

Why is this reduction so valuable? Because the decision problem often admits a greedy solution, even when the optimization problem doesn't. You trade the complexity of optimization for the simplicity of yes/no checking, repeated logarithmically many times.

It's one of the most reliable tricks in competitive programming: whenever you see "minimize the maximum" or "maximize the minimum," reach for binary search on the answer first.

Cross-References

Practice Problems

#ProblemSourceRatingWhat You Binary-Search On
1Aggressive CowsSPOJ1500Minimum distance between cows
2Splitting ArrayCSES1500Maximum subarray sum
3Kth Smallest in MatrixLeetCode1600The value X, count <= X
4Factory MachinesCSES1400Time T
5MeteorsPOI2200Parallel binary search

Recap

  • The core trick: convert "find the best X" into "can we achieve X?" and binary search over X.
  • Monotonicity is the precondition: feasibility must be monotonic in the answer value.
  • The decision version is usually greedy: binary search earns you the simplicity of yes/no checking.
  • "Minimize the maximum" and "maximize the minimum" are the strongest trigger phrases.
  • Parallel binary search handles many queries at once in O(simulation * log T) total.
  • Continuous answers: use fixed iteration count (e.g., 100), not while (lo < hi).

Built for the climb from Codeforces 1100 to 2100.