015 — Concert Tickets¶
Source¶
- Original source: CSES Sorting and Searching — ordered-multiset query
- Platform: CSES Problem Set — https://cses.fi/problemset/ ("Concert Tickets")
- Topic: heap / priority queue (ordered multiset thinking)
- Difficulty: Medium
- Pattern: Ordered multiset / "best ≤ x then remove" query
- Basic idea inherited from:
05-hashing/ sorted structures — একটা balanced ordered collection-এ predecessor query + delete
1. Problem in simple words¶
n-টা concert ticket আছে, প্রতিটার একটা দাম। m-জন customer আসে এক এক করে; প্রত্যেকের একটা সর্বোচ্চ বাজেট max_price। প্রতিটা customer তার বাজেটের মধ্যে যে ticket-টার দাম সবচেয়ে বেশি (অর্থাৎ <= max_price-এর মধ্যে largest) সেটা কেনে, আর সেই ticket চলে যায়। বাজেটের মধ্যে কোনো ticket না থাকলে সে কিছু কেনে না। প্রতিটা customer কত দাম দিল (বা না কিনলে -1) সেটা order-এ return করো।
2. Which basic idea does this inherit from?¶
ভিত 05-hashing/sorted-structures-এর ধারণা — একটা ordered collection-এ দুটো কাজ বারবার লাগে: (1) "x-এর <= সবচেয়ে বড় element কোনটা?" (predecessor query) আর (2) "সেটা remove করো"। একটা plain heap শুধু global max/min দেয়; এখানে দরকার "একটা bound-এর নিচে best", তাই একটা balanced ordered structure (বা sorted list + bisect) লাগে।
3. What is the hidden math or logic?¶
লুকানো logic: এটা একটা greedy assignment — প্রতিটা customer-কে তার বাজেটের ঠিক-নিচের সবচেয়ে দামি ticket দেওয়া কখনো খারাপ না, কারণ প্রশ্নই বলছে প্রত্যেকে নিজে নিজে সর্বোচ্চ-affordable নেয় (এটা একটা per-customer rule, global optimization না)। মূল কাজটা data-structural: প্রতিবার একটা ordered set-এ "<= x-এর largest" খুঁজে সেটা মুছে ফেলা — এটাই predecessor + delete।
4. Which data structure is needed?¶
একটা ordered multiset — দাম duplicate হতে পারে বলে multiset। Python-এ সরাসরি balanced-BST নেই; দুটো সহজ পথ: (a) দাম sort করে একটা list রেখে bisect-এ predecessor খুঁজে index-টা remove করা, অথবা (b) sortedcontainers.SortedList। নিচে standard-library bisect ব্যবহার করেছি।
5. Why this data structure fits¶
প্রতি customer-এ দরকার "<= max_price-এর largest" — heap-এর global max এখানে কাজ করে না, কারণ bound প্রতিবার আলাদা। sorted order-এ bisect_right(prices, max_price) - 1 ঠিক সেই index দেয় O(log n)-এ; তারপর remove। (SortedList হলে remove-ও O(log n); plain list থেকে remove O(n), তবে CSES-এর সরল ব্যাখ্যার জন্য তা যথেষ্ট স্পষ্ট।)
6. Real-life analogy¶
ভাবো একটা দোকানে নানা দামের ticket তাকে সাজানো — সস্তা থেকে দামি। একজন ক্রেতা এসে বলে "আমার বাজেট 4"; বিক্রেতা তাকের যে ticket-গুলো 4-এর মধ্যে, তাদের মধ্যে সবচেয়ে দামিটা তুলে দেন, সেটা তাক থেকে সরে যায়। বাজেটের নিচে কিছু না থাকলে খালি হাতে ফেরত। "এই দামের নিচে সবচেয়ে দামিটা চট করে দাও আর সরিয়ে নাও" — সেই sorted তাকটাই ordered multiset।
7. Visual explanation¶
tickets = [5,3,7,8,5], customers = [4,8,3] — দাম sort করি, প্রতি customer-এ <= budget-এর largest নিই আর মুছি:
sorted prices: [3, 5, 5, 7, 8]
customer 4: <=4 largest = 3 -> দেয় 3, মুছি prices [5, 5, 7, 8]
customer 8: <=8 largest = 8 -> দেয় 8, মুছি prices [5, 5, 7]
customer 3: <=3 largest = নেই -> দেয় -1 prices [5, 5, 7]
answers: [3, 8, -1]
8. Example input and output¶
Input : tickets=[5,3,7,8,5], customers=[4,8,3] -> Output: [3, 8, -1]
Input : tickets=[2], customers=[1] -> Output: [-1] (বাজেট কম)
Input : tickets=[2], customers=[2] -> Output: [2] (ঠিক bound)
Input : tickets=[1,1,1], customers=[1,1,1,1] -> Output: [1, 1, 1, -1] (stock শেষ)
9. Brute force thinking¶
প্রথম সরল চিন্তা: প্রতিটা customer-এর জন্য পুরো বাকি ticket-list scan করে <= max_price দামগুলোর মধ্যে largest খুঁজে নাও, সেটা remove করো, না পেলে -1।
10. Why brute force is slow¶
প্রতি customer-এ পুরো list scan মানে O(n), customer m-টা — মোট O(n·m)। CSES-এ n, m ≈ 2·10⁵ হলে এটা ~4·10¹⁰ operation, সহজেই TLE। অথচ "<= x-এর largest" একটা sorted structure-এ O(log n)-এই পাওয়া যায়।
11. Key observation¶
মূল observation: প্রশ্নটা আসলে repeated predecessor query + delete। দাম একবার sort করে রাখলে, প্রতিটা customer-এর উত্তর bisect_right(prices, budget) - 1 index-এ; সেই index ≥ 0 হলে ওই দাম দাও আর মুছে দাও, নাহলে -1। Global max heap এখানে যথেষ্ট নয় কারণ bound প্রতিবার বদলায়।
12. Optimized intuition¶
দাম sort করো একটা list-এ। প্রতিটা customer-এর জন্য bisect_right(prices, budget) দিয়ে "budget-এর চেয়ে বড় প্রথম position" পাও; তার এক আগেরটা (idx-1) হলো <= budget-এর largest। idx-1 >= 0 হলে answer = prices[idx-1], list থেকে সেই index pop করো; নাহলে answer -1। (Production-এ SortedList দিয়ে remove-ও O(log n) করা যায়।)
13. Thinking tweak¶
মোচড়: "ticket বেচা" না ভেবে ভাবো "একটা ordered multiset-এ বারবার floor(x) (x-এর <= largest) query করছি আর সেই element মুছছি।" "best price ≤ budget তারপর remove / ordered set / predecessor" — এমন কথা শুনলেই balanced ordered structure (বা sort + bisect)-এর কথা মাথায় আসা উচিত; plain heap এখানে কম পড়বে।
14. Step-by-step dry run¶
Input tickets=[1,1,1], customers=[1,1,1,1]:
- sorted prices =
[1, 1, 1], answers = [] - customer 1:
bisect_right([1,1,1], 1) = 3→ idx-1 = 2 ≥ 0 → answer 1, pop index 2 → prices[1,1] - customer 1:
bisect_right([1,1], 1) = 2→ idx-1 = 1 → answer 1, pop index 1 → prices[1] - customer 1:
bisect_right([1], 1) = 1→ idx-1 = 0 → answer 1, pop index 0 → prices[] - customer 1:
bisect_right([], 1) = 0→ idx-1 = −1 < 0 → answer −1 - return [1, 1, 1, −1]
15. Python solution¶
import bisect
def concert_tickets(tickets, customers):
prices = sorted(tickets) # ordered multiset (sorted list)
answers = []
for budget in customers:
# budget-এর <= largest দামের index = bisect_right - 1
idx = bisect.bisect_right(prices, budget) - 1
if idx >= 0:
answers.append(prices[idx])
prices.pop(idx) # সেই ticket বিক্রি -> remove
else:
answers.append(-1) # বাজেটের মধ্যে কিছু নেই
return answers
# ---- tests ----
assert concert_tickets([5, 3, 7, 8, 5], [4, 8, 3]) == [3, 8, -1]
assert concert_tickets([2], [1]) == [-1] # বাজেট কম
assert concert_tickets([2], [2]) == [2] # ঠিক bound
assert concert_tickets([1, 1, 1], [1, 1, 1, 1]) == [1, 1, 1, -1] # stock শেষ
assert concert_tickets([10, 20, 30], [25, 25, 25]) == [20, 10, -1] # বড়টা আগে যায়
assert concert_tickets([], [5, 6]) == [-1, -1] # ticket নেই
print("সব test pass!")
16. Line-by-line code explanation¶
prices = sorted(tickets)— দামগুলোকে ছোট-থেকে-বড় সাজানো ordered multiset বানায়।for budget in customers:— customer-রা যে order-এ আসে সেই order-এই process।idx = bisect.bisect_right(prices, budget) - 1—budget-এর চেয়ে বড় প্রথম position-এর ঠিক আগেরটা =<= budget-এর largest-এর index।if idx >= 0:— অমন একটা ticket আছে কিনা; না থাকলে idx হবে −1।answers.append(prices[idx]); prices.pop(idx)— সেই দাম record করো আর ticket-টা remove (বিক্রি হয়ে গেল)।else: answers.append(-1)— বাজেটের মধ্যে কিছু নেই, customer কিছু কেনে না।
17. Output walkthrough¶
concert_tickets([1,1,1], [1,1,1,1]) section 14-এর dry run মেনে [1,1,1,−1] দেয় — চতুর্থ জন stock শেষ পায়। main example [3,8,−1]; [10,20,30] বাজেট 25-এ আগে 20 তারপর 10, শেষে −1; খালি ticket-list-এ সব −1। output প্রতিটা customer-এর নির্দিষ্ট ক্রমে, তাই সরাসরি সমতা assert। সব pass-এর পরে print হয়: সব test pass!।
18. Time complexity¶
O((n + m) log n) আদর্শ ordered-multiset (যেমন SortedList)-এ — sort O(n log n), প্রতি customer query+delete O(log n)। নিচের সরল version-এ list.pop(idx) O(n) বলে worst case O(n·m); ব্যাখ্যা স্পষ্ট রাখতে এটা রাখা হয়েছে, কিন্তু বড় input-এ SortedList ব্যবহার করো।
19. Space complexity¶
O(n) — sorted prices list, আর O(m) output। সব মিলিয়ে input-এর সমানুপাতিক।
20. Common mistakes¶
bisect_leftব্যবহার করা যেখানেbisect_rightদরকার — তখন budget-এর সমান দামের ticket miss হতে পারে।- remove করতে ভুলে যাওয়া — তাহলে একই ticket একাধিক customer-কে বিক্রি হয়ে যায়।
- বড় input-এ plain-list
pop(idx)-এর O(n) cost ভুলে যাওয়া — TLE; তখনSortedList/BIT দরকার। - empty list-এ
idx-1 = -1কে valid index ভাবা — Python-এprices[-1]ভুল element দেবে; তাইidx >= 0guard জরুরি।
21. Which basic problem this inherits from¶
ভিত্তি 05-hashing/sorted-structures-এর ordered-collection ধারণা — predecessor query + delete। আর 08-এর heap-ধারণার সীমা চেনায়: heap শুধু global extreme দেয়, "একটা bound-এর নিচে best" দিতে পারে না; তখন balanced ordered structure লাগে।
22. Similar harder problems¶
- Towers (CSES) — https://cses.fi/problemset/ ("Towers"; multiset-এ "strictly larger" খুঁজে replace)
- Nested Ranges Count — https://cses.fi/problemset/ (ordered structure + counting)
- Sliding Window Median — https://leetcode.com/problems/sliding-window-median/ (এই tracker-এর #21; ordered-structure/two-heaps)
23. Pattern learned¶
Ordered multiset query: যখন বারবার "একটা bound-এর <=/>= best element নাও আর মুছে ফেলো" লাগে, তখন heap যথেষ্ট নয় — একটা balanced ordered structure (বা sort + bisect) দরকার, যেটা predecessor/successor query আর delete দুটোই O(log n)-এ দেয়।
24. Final summary¶
ভবিষ্যতের আমাকে: "প্রতি customer তার বাজেটের নিচের সবচেয়ে দামি ticket নেয়, ticket সরে যায়" = ordered multiset-এ bisect_right - 1 predecessor + delete। ছোট input-এ sort+list চলে, বড়তে SortedList/BIT। মনে রেখো — plain heap এখানে কম পড়ে, কারণ bound প্রতিবার বদলায়।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।