098 — Aggressive Cows¶
এখন পর্যন্ত সব ছিল minimize — "সবচেয়ে ছোট valid উত্তর"। এই problem-এ প্রথমবার মাথাটা উল্টাতে হবে: maximize — "সবচেয়ে বড় valid উত্তর"। গরুগুলো ঝগড়াটে, তাই দুই গরুর মাঝের সবচেয়ে কাছের দূরত্ব যত বড় করা যায়। সিঁড়িটা উল্টে যায় (T T T F F F), আর binary search-এর move-ও উল্টো। এই উল্টোটা ভালো করে বুঝলে minimize আর maximize আর কখনো গুলোবে না।
Source¶
- Original source: SPOJ AGGRCOW
- Platform: SPOJ — https://www.spoj.com/problems/AGGRCOW/
- Topic: Math-based programming fundamentals → Level 7: Binary Search on Answer
- Difficulty: Medium
- Pattern: BSoA maximize (সবচেয়ে বড় feasible উত্তর; সিঁড়ি TTTFFF)
- Basic idea inherited from: 096 — Minimum Eating Speed
1. Problem in simple words¶
একটা সরলরেখায় কতগুলো গোয়ালঘর (stall) আছে, stalls[i] হলো i-তম stall-এর অবস্থান (একটা সংখ্যা)। তোমার কাছে c টা গরু, প্রত্যেককে আলাদা stall-এ রাখতে হবে।
গরুগুলো আগ্রাসী — কাছাকাছি থাকলে ঝগড়া করে। তাই তুমি চাও যেকোনো দুই গরুর মধ্যে সবচেয়ে কম দূরত্বটা যত বড় সম্ভব হোক। প্রশ্ন: c টা গরু এমনভাবে বসালে, এই minimum দূরত্বের সর্বোচ্চ মান কত?
মানে আমরা minimum gap-টাকে maximize করছি — তাই একে "maximize the minimum" বলে।
2. What is the math idea?¶
maximize ঘরানার BSoA। উত্তর (সবচেয়ে বড় minimum gap) সরাসরি বের না করে, একটা gap g guess করি আর check: "প্রতি দুই গরুর মাঝে অন্তত g দূরত্ব রেখে c টা গরু বসানো যায়?"
observation — g ছোট হলে বসানো সহজ, বড় হলে কঠিন। তাই সিঁড়ি এবার উল্টো: T T T F F F — ছোট g-তে পারা যায় (T), একটা জায়গার পর আর যায় না (F)। শেষ T-টাই সবচেয়ে বড় feasible gap — আমাদের উত্তর।
3. Which basic idea does this inherit from?¶
096 (Minimum Eating Speed) থেকে — answer space + feasibility check + monotonicity, সেই তিন উপাদান এখানেও। কিন্তু দুটো বড় বদল:
- দিক উল্টো: 096-এ সিঁড়ি F F F T T T, প্রথম T খুঁজতাম; এখানে T T T F F F, শেষ T খুঁজি।
- move উল্টো: 096-এ check পাশ করলে
hi = mid(ছোট খুঁজি); এখানে পাশ করলেlo = mid(আরও বড় খুঁজি)। আর তাইmidউপরে ঝোঁকাতে হয় ((lo+hi+1)//2), নইলে infinite loop।
check-এর ভেতরের greedy (097-এর মতো এক পাস) এখানেও — কিন্তু এবার "একটা বসাও, g দূরত্ব পেলেই পরেরটা"।
4. Real-life analogy¶
ভাবো একটা লম্বা বেঞ্চে কয়েকটা নির্দিষ্ট জায়গায় বসা যায় (stall), আর তোমরা ক'জন বন্ধু যারা একে অপরের গা ঘেঁষে বসতে চাও না — যত দূরে দূরে, তত ভালো।
তুমি চেষ্টা করো — "সবাই অন্তত 3 হাত দূরে বসতে পারব?" যদি জায়গা কুলোয়, তাহলে ভাবো "5 হাত দূরে?" আরও ছড়িয়ে দেখো। একসময় এমন দূরত্ব আসে যে আর সবাইকে বসানো যায় না — তার ঠিক আগেরটাই সর্বোচ্চ সম্ভব দূরত্ব। দূরত্ব = gap g, বন্ধু = c টা গরু।
5. Visual explanation¶
stalls = [1, 2, 4, 8, 9], c = 3। প্রতিটা gap g-এর জন্য "অন্তত g রেখে 3টা বসে?":
g : 1 2 3 4 | 5 6 7 ...
বসানো যায়?: T T T T | F F F ... check(g) = "gap ≥ g রেখে 3টা গরু বসে?"
↑
শেষ T → উত্তর = 3
g=3: stall 1-এ বসাও → 4 (4-1=3≥3) → 8 (8-4=4≥3) → 3টা বসল ✓ T
g=4: stall 1-এ বসাও → 8 (8-1=7≥4, কিন্তু 4 বাদ গেল কারণ 4-1=3<4) → 9 শেষ, মোট 2টা < 3 ✗ F
সিঁড়ি T T T F F F (096-এর উল্টো) — g বাড়লে বসানো কঠিন, শেষ T = সবচেয়ে বড় feasible gap।
6. Example input and output¶
stalls (sorted) c -> max min-gap (ব্যাখ্যা)
---------------------------------------------------
[1, 2, 4, 8, 9] 3 -> 3 (1, 4, 8 → gap 3, 4)
[1, 2, 8, 4, 9] 3 -> 3 (sort করে নিতে হয় → একই)
[0, 3, 4, 7, 10, 9] 4 -> 3 (0, 3, 7, 10 → min gap 3)
[1, 5] 2 -> 4 (দুই গরু দুই প্রান্তে → 5-1)
[1, 2, 3, 4, 5] 5 -> 1 (সব stall-এ গরু → পাশাপাশি gap 1)
edge: c == len(stalls) হলে প্রতিটা stall-এ একটা গরু বসাতেই হবে → min gap = পরপর দুই stall-এর সবচেয়ে ছোট ফারাক। আর c == 2 হলে দুই প্রান্তে বসানোই সেরা → gap = max - min।
7. Brute force thinking¶
stall sort করো। তারপর gap g = 1 থেকে শুরু করে এক এক করে বাড়াও; প্রতি g-তে greedy-তে চেক করো c টা বসে কিনা — প্রথম যে g-তে আর বসে না, তার ঠিক আগেরটাই উত্তর:
def aggressive_cows_brute(stalls, c):
stalls = sorted(stalls)
def can(g):
cnt, last = 1, stalls[0] # প্রথম গরু প্রথম stall-এ
for s in stalls[1:]:
if s - last >= g:
cnt += 1; last = s
return cnt >= c
g = 1
while can(g): # যতক্ষণ বসে, gap বাড়াও
g += 1
return g - 1 # প্রথম "বসে না"-র আগেরটা
can(g) greedy: প্রথম stall-এ একটা বসাও, তারপর যেখানেই last গরু থেকে অন্তত g দূরত্ব পাও, সেখানেই পরের গরু। মোট c টা বসলে feasible। তারপর g বাড়িয়ে শেষ feasible-টা ধরি।
8. Why brute force may be slow¶
g-এর পরিসর 1 থেকে max - min পর্যন্ত — যা 10^9 স্কেল হতে পারে। প্রতি g-তে greedy O(n)। তাই O(n · (max - min)) — অচল।
max - min ≈ 10^9, n = 10^5 হলে:
brute force : ~10^14 operation (অসম্ভব ধীর, TLE)
binary search : ~30 × 10^5 ≈ 3×10^6 (O(n log(range)), দ্রুত)
মূল শিক্ষা: can(g) monotonic (T...F) — তাই g এক এক করে না বাড়িয়ে binary search-এ অর্ধেক বাদ।
9. Better thinking¶
মূল insight: can(g)-এর সিঁড়ি T T T F F F — monotonic। শেষ T খুঁজি, maximize template-এ:
def aggressive_cows(stalls, c):
stalls = sorted(stalls)
def can(g): # gap ≥ g রেখে c টা বসে?
cnt, last = 1, stalls[0]
for s in stalls[1:]:
if s - last >= g:
cnt += 1; last = s
return cnt >= c
lo, hi = 0, stalls[-1] - stalls[0] # gap 0 সবসময় হয়; সর্বোচ্চ = পরিসর
while lo < hi:
mid = (lo + hi + 1) // 2 # উপরে ঝোঁকানো mid (lo = mid থাকায়)!
if can(mid):
lo = mid # পারা যায় → আরও বড় চেষ্টা (mid রেখে দাও)
else:
hi = mid - 1 # পারা যায় না → ছোট লাগবে
return lo
প্রতি ধাপে অর্ধেক, প্রতি check O(n) → O(n log(range))।
10. Thinking tweak¶
আসল মোচড় — minimize থেকে maximize-এ গেলে দুটো জিনিস একসাথে উল্টাও:
- move: check পাশ করলে
lo = mid(আরও বড় চাই, mid রেখে দাও), fail করলেhi = mid - 1। (096-এ ছিল উল্টো।) - mid-এর ঝোঁক: যেহেতু
lo = mid, তাইmid = (lo + hi + 1) // 2— উপরে ঝোঁকানো। নইলেhi - lo == 1অবস্থায়mid = loহয়েlo = midকোনো অগ্রগতি করে না → চিরঘূর্ণন (infinite loop)।
নিয়মটা গেঁথে নাও: lo = mid থাকলে mid উপরে ঝোঁকাও (+1); hi = mid থাকলে নিচে। সিঁড়ির ছবি এঁকে দেখো T কোন পাশে জমে — সেই দিকেই চাপতে হবে।
11. Step-by-step dry run¶
stalls = [1, 2, 4, 8, 9], c = 3 (sorted; lo=0, hi=9-1=8):
| step | lo | hi | mid=(lo+hi+1)//2 | can(mid)? (কতটা বসে) | সিদ্ধান্ত |
|---|---|---|---|---|---|
| 1 | 0 | 8 | 4 | 2টা < 3 → না | hi = mid - 1 = 3 |
| 2 | 0 | 3 | 2 | 3টা (1,4,8) → হ্যাঁ | lo = mid = 2 |
| 3 | 2 | 3 | 3 | 3টা (1,4,8) → হ্যাঁ | lo = mid = 3 |
| — | 3 | 3 | — | — | lo == hi = 3 → return 3 |
উত্তর 3 ✓। লক্ষ করো step 1-এ mid উপরে ঝোঁকানো (4, নিচে হলে 4-ই হতো এখানে) — আর step 3-এ lo = mid করেও mid উপরে ঝোঁকা বলে hi - lo == 0 হয়ে loop থামল, আটকে গেল না।
12. Python solution¶
def aggressive_cows(stalls, c):
"""c টা গরু এমনভাবে বসাও যাতে minimum gap সবচেয়ে বড় হয়।"""
stalls = sorted(stalls) # রেখার উপর সাজিয়ে নিই
def can(g): # gap ≥ g রেখে c টা গরু বসে?
cnt, last = 1, stalls[0] # প্রথম গরু প্রথম stall-এ
for s in stalls[1:]:
if s - last >= g: # যথেষ্ট দূর → পরের গরু এখানে
cnt += 1
last = s
return cnt >= c
lo, hi = 0, stalls[-1] - stalls[0] # gap 0 always; max = দুই প্রান্তের ফারাক
while lo < hi:
mid = (lo + hi + 1) // 2 # lo=mid থাকায় mid উপরে ঝোঁকানো
if can(mid):
lo = mid # পারা যায় → আরও বড় চেষ্টা
else:
hi = mid - 1 # পারা যায় না → ছোট লাগবে
return lo
def cows_brute(stalls, c): # ছোট input-এ cross-check
stalls = sorted(stalls)
def can(g):
cnt, last = 1, stalls[0]
for s in stalls[1:]:
if s - last >= g:
cnt += 1; last = s
return cnt >= c
g = 0
while can(g + 1): # পরের gap-ও কি চলে?
g += 1
return g
# --- ছোট test গুলো (সব pass করা উচিত) ---
assert aggressive_cows([1, 2, 4, 8, 9], 3) == 3
assert aggressive_cows([1, 2, 8, 4, 9], 3) == 3 # unsorted ইনপুট
assert aggressive_cows([0, 3, 4, 7, 10, 9], 4) == 3
assert aggressive_cows([1, 5], 2) == 4 # দুই প্রান্তে
assert aggressive_cows([1, 2, 3, 4, 5], 5) == 1 # সব stall-এ
assert aggressive_cows([10, 1, 2, 7, 5], 3) == 4 # SPOJ sample
# brute force-এর সাথে random cross-check
import random
random.seed(13)
for _ in range(300):
n = random.randint(2, 8)
stalls = random.sample(range(0, 40), n) # আলাদা position
c = random.randint(2, n)
assert aggressive_cows(stalls, c) == cows_brute(stalls, c)
# boundary: উত্তর g-তে বসে, g+1-এ বসে না
stalls, c = [1, 2, 4, 8, 9], 3
g = aggressive_cows(stalls, c)
def fits(arr, gap, k):
cnt, last = 1, arr[0]
for s in arr[1:]:
if s - last >= gap:
cnt += 1; last = s
return cnt >= k
ss = sorted(stalls)
assert fits(ss, g, c)
assert not fits(ss, g + 1, c)
print(aggressive_cows([1, 2, 4, 8, 9], 3)) # 3
print(aggressive_cows([10, 1, 2, 7, 5], 3)) # 4
print("সব test pass!")
13. Line-by-line explanation¶
greedy বসানো কাজ করে শুধু sorted রেখায় — তাই আগে সাজাই। ইনপুট unsorted এলেও এই লাইন সামলে নেয়।
প্রথম গরু সবসময় প্রথম (সবচেয়ে বাঁয়ের) stall-এ — এটাই সবচেয়ে বেশি জায়গা ছাড়ে। cnt = বসানো গরুর সংখ্যা, last = শেষ বসানো গরুর position।
চলতি stall শেষ গরু থেকে অন্তত g দূরে — তাই এখানে পরের গরু বসাও, last আপডেট। যথেষ্ট দূরে না হলে এই stall এড়িয়ে যাই।
answer space। gap 0 সবসময় সম্ভব (T), তাই lo = 0। সর্বোচ্চ সম্ভব gap দুই প্রান্তের ফারাক (এর বেশি দূরত্ব রাখা অসম্ভব), তাই hi = stalls[-1] - stalls[0]।
উপরে ঝোঁকানো mid — কারণ নিচে lo = mid। +1 না দিলে hi - lo == 1-এ infinite loop। (maximize-এর স্বাক্ষর।)
maximize move: পারলে আরও বড় খুঁজি (mid রেখে lo = mid), না পারলে ছোট লাগবে (hi = mid - 1)। 096-এর ঠিক উল্টো।
cross-check-এ random brute force আর boundary (g বসে, g+1 বসে না)। সব মিললে সব test pass!।
14. Output walkthrough¶
চালালে যা ছাপবে:
প্রথম: aggressive_cows([1,2,4,8,9], 3) = 3 (dry run-এর ফল)। দ্বিতীয়: [10,1,2,7,5] c=3-এ = 4 (sort → [1,2,5,7,10]; 1,5,10 → min gap 4)। আগের সব assert — random cross-check (300 কেস) আর boundary যাচাই — চুপচাপ pass। সবশেষে সব test pass!।
15. Time complexity¶
O(n log(range)) — sort-এ O(n log n), তারপর answer space [0, max-min]-এ binary search (log(range) ধাপ), প্রতি ধাপে check O(n)। মোট O(n log n + n log(range))।
16. Space complexity¶
O(1) (sort-কে in-place ধরলে; Python-এর sorted আসলে O(n) extra বানায়) — check-এ শুধু cnt, last; বাড়তি কাঠামো নেই।
17. Common mistakes¶
- sort করতে ভুলে যাওয়া — greedy বসানো sorted রেখা ছাড়া অর্থহীন; ভুল উত্তর। সবার আগে sort।
- maximize-এর mid নিচে ঝোঁকানো —
lo = midথাকলেmid = (lo+hi)//2দিলেhi-lo==1-এ infinite loop।+1দাও। - minimize-এর move লিখে ফেলা — পাশ করলে এখানে
lo = mid(বড় খোঁজো),hi = midনা। সিঁড়ি TTTFFF মনে রাখো। return gvsreturn g - 1(brute force-এ) — শেষ T কোনটা সেটা সাবধানে; binary search version এই ফাঁদ এড়ায় কারণlo-ই শেষ T।- প্রথম গরু প্রথম stall-এ না বসানো — greedy-তে প্রথমটা সবচেয়ে বাঁয়ে বসানোই optimal; অন্য কোথাও শুরু করলে কম গরু বসে।
18. Harder problems that inherit this idea¶
- LeetCode — Magnetic Force Between Two Balls (https://leetcode.com/problems/magnetic-force-between-two-balls/) — হুবহু aggressive cows (ball = গরু, force = gap); একই maximize-the-minimum।
- LeetCode — Divide Chocolate (https://leetcode.com/problems/divide-chocolate/) — সবচেয়ে ছোট টুকরোটা যত বড় সম্ভব; একই উল্টো সিঁড়ি।
- 102 (Kth Smallest in Multiplication Table) — এই repo-রই পরের ধাপ; সেখানে দিক আলাদা কিন্তু "guess + count/greedy check" চিন্তা একই পরিবার।
19. Pattern learned¶
BSoA maximize (largest feasible answer; TTTFFF) — check পাশ → lo = mid, fail → hi = mid - 1, আর mid = (lo+hi+1)//2 (উপরে ঝোঁকা)। বড় শিক্ষা: "maximize the minimum" (বা "সবচেয়ে বড় valid X") শুনলেই — সিঁড়ি উল্টে যায়, শেষ T খোঁজো, move আর mid-এর ঝোঁক দুটোই উল্টাও। minimize আর maximize-এর এই জোড়া চিনে রাখাই এই level-এর আসল পরিপক্বতা।
20. Final summary¶
ভবিষ্যতের আমাকে এক লাইনে: "Aggressive Cows = maximize BSoA; answer = min-gap, check = greedy-তে gap≥g রেখে c টা বসে?, সিঁড়ি TTTFFF, শেষ T-ই উত্তর। sort আগে; পাশ→lo=mid, fail→hi=mid-1, mid=(lo+hi+1)//2 (উপরে ঝোঁকা — নইলে infinite loop)। 'maximize the minimum' = এই ছক।"
পরের ধাপ → 099 — Allocate Minimum Pages (আবার minimize, কিন্তু "minimize the maximum load")। সম্পর্কিত → 096 — Minimum Eating Speed (এর উল্টো ঘরানা)।
ফিরে দেখা: এই level-এর map → ../README.md · এই note-টা যে ছকে লেখা → ../../../templates/math-problem-note-template.md।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখো; problem statement copy কোরো না; official link শুধু attribution-এর জন্য রাখো; "asked by Google/Amazon" এমন দাবি কোরো না — বরং "Google-style pattern" / "common interview pattern" লেখো।