SPOJ Medium

Aggressive Cows

Place k cows in stalls (positions on a line) maximizing the minimum distance between any two cows.

binary-searchgreedy
Open on SPOJ ↗
02

Intuition

Mirror image of Allocate Pages: 'can cows be placed with every gap ≥ d?' is a greedy sweep, and shrinking d only makes it easier — monotone again. Binary search the largest feasible d.

How to spot this pattern

The giveaway is "maximise the minimum" (or "minimise the maximum"). You can't compute that directly, but you can check a guess: given a distance d, greedily place cows and see if k fit. That check is monotone — if d works, every smaller d works too — so binary search the answer. Allocate-minimum-pages, split-array-largest-sum and koko-eating-bananas are all this same template.

03

Approach

1

Feasibility by greedy placement

Sort stalls; place the first cow in stall 0, then each next cow in the first stall ≥ d beyond the previous. Count placed cows.

2

Binary search on distance

Search d in [1, span]. Feasible → try bigger (lo = mid+1, remember mid); infeasible → smaller.

3

Max-min duality

Maximize-the-minimum → search the answer and test with ≥; minimize-the-maximum (pages) tests with ≤. Same skeleton.

04

Solution & live demo

1def aggressive_cows(stalls, k):
2 stalls.sort()
3 def can_place(d):
4 count, last = 1, stalls[0]
5 for s in stalls[1:]:
6 if s - last >= d:
7 count += 1; last = s
8 return count >= k
9 lo, hi, best = 1, stalls[-1] - stalls[0], 0
10 while lo <= hi:
11 mid = (lo + hi) // 2
12 if can_place(mid): best = mid; lo = mid + 1
13 else: hi = mid - 1
14 return best
05

Common pitfalls

Forgetting to sort the stalls

✗ Wrong
def can_place(d):
    count, last = 1, stalls[0]
✓ Right
stalls.sort()
def can_place(d):
    count, last = 1, stalls[0]

The greedy placement assumes each stall is further right than the last, so s - last measures a real gap. On unsorted input that difference can be negative and the count is meaningless.

Searching over stall indices instead of distances

✗ Wrong
lo, hi = 0, len(stalls) - 1
✓ Right
lo, hi = 1, stalls[-1] - stalls[0]

The thing being searched is the answer — a distance — not a position in the array. The range runs from 1 to the widest possible separation; binary searching indices answers a different question entirely.

Moving the bound the wrong way on success

✗ Wrong
if can_place(mid): hi = mid - 1
else: lo = mid + 1
✓ Right
if can_place(mid): best = mid; lo = mid + 1
else: hi = mid - 1

A feasible distance is a candidate you want to beat, since bigger is better here. Shrinking hi on success searches downward and returns the smallest workable distance rather than the largest.

06

Edge cases

k = 2

Answer is simply the span — first and last stall — and the search finds it.

Stalls unsorted on input

Sort first; greedy placement depends on order.

07

Complexity

Time
O(n log D)
Space
O(1)
D = span of stall positions.