Hotel Queries
Problem Overview
| Attribute | Value |
|---|---|
| Source | CSES 1143 - Hotel Queries |
| Difficulty | Medium |
| Category | Range Queries |
| Time Limit | 1 second |
| Key Technique | Segment Tree with Maximum + Descend Search |
Learning Goals
After solving this problem, you will be able to:
- Implement a segment tree that tracks maximum values
- Use segment tree structure to find the first position satisfying a condition
- Combine point updates with efficient search operations
- Apply “descend” technique to search within a segment tree
Problem Statement
Problem: There are n hotels with specified room capacities. For each of m groups of tourists, find the first hotel (leftmost) that has enough rooms to accommodate the entire group, book those rooms, and report the hotel number (or 0 if no hotel can accommodate them).
Input:
- Line 1: Two integers n and m (number of hotels, number of groups)
- Line 2: n integers h_1, h_2, …, h_n (room count in each hotel)
- Next m lines: One integer r_i (size of each group)
Output:
- For each group, print the hotel number (1-indexed) where they are assigned, or 0 if impossible
Constraints:
- 1 <= n, m <= 2 * 10^5
- 1 <= h_i <= 10^9
- 1 <= r_i <= 10^9
Example
Input:
8 5
3 2 4 1 5 5 2 6
4 4 7 1 1
Output:
3 5 0 1 1
Explanation:
- Group 1 (size 4): Hotels have [3,2,4,1,5,5,2,6]. First hotel with >= 4 rooms is hotel 3 (4 rooms). Book it: [3,2,0,1,5,5,2,6]. Output: 3
- Group 2 (size 4): First hotel with >= 4 rooms is hotel 5 (5 rooms). Book it: [3,2,0,1,1,5,2,6]. Output: 5
- Group 3 (size 7): No hotel has >= 7 rooms. Output: 0
- Group 4 (size 1): First hotel with >= 1 room is hotel 1 (3 rooms). Book it: [2,2,0,1,1,5,2,6]. Output: 1
- Group 5 (size 1): First hotel with >= 1 room is hotel 1 (2 rooms). Book it: [1,2,0,1,1,5,2,6]. Output: 1
Intuition: How to Think About This Problem
Pattern Recognition
Key Question: We need to repeatedly find the “first position with value >= X” and then update that position. What data structure supports both operations efficiently?
This is a classic application of segment trees. A segment tree with maximum allows us to:
- Check if any hotel in a range has enough rooms (compare range max with required rooms)
- Navigate down the tree to find the leftmost such hotel
- Update a single hotel’s room count after booking
Breaking Down the Problem
- What are we looking for? The leftmost hotel with rooms >= group size
- What information do we have? Current room counts for all hotels
- What’s the relationship between input and output? For each query, find first valid position, update it, return the position
Why a Segment Tree?
Think of the segment tree as a tournament bracket where each node stores the maximum of its children. To find the leftmost hotel with enough rooms:
- If the root’s max is less than needed, no hotel works (return 0)
- Otherwise, always try the left subtree first (to get leftmost)
- Only go right if left subtree’s max is insufficient
Solution 1: Brute Force
Idea
For each group, linearly scan hotels from left to right to find the first one with enough rooms.
Algorithm
- For each group of size r:
- Iterate through hotels 1 to n
- If hotel i has >= r rooms, book it (subtract r), output i, move to next group
- If no hotel found, output 0
Code
def solve_brute_force(n, m, hotels, groups):
"""
Brute force: linear scan for each query.
Time: O(m * n)
Space: O(1) extra
"""
results = []
for group_size in groups:
found = 0
for i in range(n):
if hotels[i] >= group_size:
hotels[i] -= group_size
found = i + 1 # 1-indexed
break
results.append(found)
return results
Complexity
| Metric | Value | Explanation |
|---|---|---|
| Time | O(m * n) | Each of m queries scans up to n hotels |
| Space | O(1) | Only using input array |
Why This Works (But Is Slow)
The correctness is obvious: we check every hotel in order. However, with n, m up to 210^5, this gives 410^10 operations - far too slow.
Solution 2: Optimal Solution with Segment Tree
Key Insight
The Trick: Build a segment tree storing maximum room count in each range. To find the leftmost hotel with enough rooms, descend from root, always preferring the left child.
Segment Tree Node Meaning
| Node | Meaning |
|---|---|
tree[v] |
Maximum room count among all hotels in the range covered by node v |
In plain English: Each node tells us “the best hotel in my range has this many rooms available.”
Operations Needed
- Query + Update Combined: Find leftmost position with value >= X, then subtract X from that position
- This is done in a single tree descent - no separate query and update phases
Algorithm
- Build segment tree where each node stores max of its range
- For each group of size r:
- If root’s max < r: output 0 (no hotel works)
- Otherwise, descend the tree:
- At each internal node, go left if left child’s max >= r, else go right
- At leaf, update value (subtract r) and propagate max changes up
- Output the found position
Dry Run Example
Let’s trace with hotels = [3, 2, 4, 1, 5, 5, 2, 6] and first query r = 4:
Initial segment tree (showing max values):
[6] <- root: max of all = 6
/ \
[4] [6] <- max of [0-3] = 4, max of [4-7] = 6
/ \ / \
[3] [4] [5] [6] <- max of pairs
/ \ / \ / \ / \
3 2 4 1 5 5 2 6 <- leaf values (hotels)
^ ^ ^ ^ ^ ^ ^ ^
h1 h2 h3 h4 h5 h6 h7 h8
Query: Find leftmost hotel with >= 4 rooms
Step 1: At root [6], max=6 >= 4, so solution exists
Step 2: Check left child [4], max=4 >= 4, go LEFT
Step 3: Check left child of [4], which is [3], max=3 < 4, go RIGHT
Step 4: At node [4] (covering h3-h4), left child is leaf h3=4 >= 4, go LEFT
Step 5: Reached leaf h3 (index 2, 1-indexed = 3)
Update: h3 = 4 - 4 = 0
After update, tree becomes:
[6]
/ \
[3] [6] <- updated from 4 to 3
/ \ / \
[3] [1] [5] [6] <- updated from 4 to 1
/ \ / \
3 2 0 1 <- h3 updated to 0
Output: 3
Visual Diagram
Segment Tree Structure (array-based, 1-indexed internal):
For n=8 hotels, tree size = 2*8 = 16
Index: 1
/ \
2 3
/ \ / \
4 5 6 7
/\ /\ /\ /\
8 9 10 11 12 13 14 15 <- leaves store hotel rooms
Leaf index i corresponds to hotel (i - n + 1) in 1-indexed terms
Or: leaf at tree[n + i] stores hotel[i] (0-indexed)
Code
Python Solution:
import sys
from typing import List
def solve():
input_data = sys.stdin.read().split()
idx = 0
n = int(input_data[idx]); idx += 1
m = int(input_data[idx]); idx += 1
hotels = [int(input_data[idx + i]) for i in range(n)]
idx += n
groups = [int(input_data[idx + i]) for i in range(m)]
# Build segment tree (1-indexed, size 2*n)
# tree[i] for i >= n are leaves (hotels)
# tree[i] for i < n are internal nodes (max of children)
tree = [0] * (2 * n)
# Initialize leaves
for i in range(n):
tree[n + i] = hotels[i]
# Build internal nodes (bottom-up)
for i in range(n - 1, 0, -1):
tree[i] = max(tree[2 * i], tree[2 * i + 1])
results = []
for group_size in groups:
# Check if any hotel can accommodate
if tree[1] < group_size:
results.append(0)
continue
# Descend to find leftmost hotel with enough rooms
v = 1
while v < n:
if tree[2 * v] >= group_size:
v = 2 * v # go left
else:
v = 2 * v + 1 # go right
# v is now a leaf, hotel index = v - n (0-indexed), output v - n + 1 (1-indexed)
hotel_idx = v - n
results.append(hotel_idx + 1)
# Update: subtract group_size from this hotel
tree[v] -= group_size
# Propagate update upward
v //= 2
while v >= 1:
tree[v] = max(tree[2 * v], tree[2 * v + 1])
v //= 2
print(' '.join(map(str, results)))
if __name__ == "__main__":
solve()
Complexity
| Metric | Value | Explanation |
|---|---|---|
| Time | O((n + m) * log n) | Build tree O(n), each query O(log n) |
| Space | O(n) | Segment tree of size 2n |
Common Mistakes
Mistake 1: Using Binary Search Instead of Tree Descent
# WRONG APPROACH
def find_hotel_binary_search(tree, n, required):
lo, hi = 0, n - 1
while lo < hi:
mid = (lo + hi) // 2
# This doesn't work! Can't query "max in [0, mid]" efficiently
# without understanding segment tree structure
Problem: Standard binary search doesn’t leverage segment tree structure efficiently. Fix: Use the descent approach - check left child first, go right only if necessary.
Mistake 2: Forgetting to Propagate Updates
# WRONG
tree[v] -= group_size # Update leaf
# Missing: propagate change to ancestors!
# CORRECT
tree[v] -= group_size
v //= 2
while v >= 1:
tree[v] = max(tree[2 * v], tree[2 * v + 1])
v //= 2
Problem: Parent nodes still have stale max values. Fix: Always update ancestors after modifying a leaf.
Mistake 3: Off-by-One Indexing
# WRONG - mixing 0-indexed and 1-indexed
hotel_number = v - n # This is 0-indexed!
# CORRECT
hotel_number = v - n + 1 # Convert to 1-indexed for output
Mistake 4: Wrong Tree Size for Non-Power-of-2
# Potential issue: if n is not power of 2
# The simple 2*n approach works, but be careful with indexing
# Safe approach: pad to next power of 2, or use this exact method
# which works for any n
Edge Cases
| Case | Input | Expected Output | Why |
|---|---|---|---|
| No room available | n=2, hotels=[1,1], group=5 |
0 | Max room count < required |
| All groups same hotel | n=3, hotels=[10,1,1], groups=[1,1,1] |
1 1 1 | First hotel keeps having enough |
| Single hotel | n=1, hotels=[5], groups=[3,3] |
1 0 | Second group can’t fit |
| Large values | hotels=[10^9], group=10^9 |
1 | Handle large numbers |
| Exact fit | hotels=[4,3], group=4 |
1 | Exact match works |
When to Use This Pattern
Use This Approach When:
- You need to find the first/leftmost position satisfying a condition
- The condition can be checked using range aggregates (max, min, sum, etc.)
- You need point updates after finding the position
- Multiple queries require efficient repeated searches
Don’t Use When:
- Simple linear scan is fast enough (small n, m)
- You need range updates (consider lazy propagation)
- The “first position” logic doesn’t fit segment tree descent
Pattern Recognition Checklist:
- Need to find first position with value >= X? Segment tree with max + descent
- Need to find first position with prefix sum >= X? Segment tree with sum + descent
- Need range max/min queries without position finding? Standard segment tree query
- Need to support range updates too? Segment tree with lazy propagation
Related Problems
Easier (Do These First)
| Problem | Why It Helps |
|---|---|
| Static Range Minimum Queries (CSES 1647) | Basic segment tree / sparse table for range min |
| Dynamic Range Sum Queries (CSES 1648) | Segment tree with point updates |
Similar Difficulty
| Problem | Key Difference |
|---|---|
| Dynamic Range Minimum Queries (CSES 1649) | Range min instead of max, no descent needed |
| Range Update Queries (CSES 1651) | Range updates instead of point updates |
| List Removals (CSES 1749) | Similar descent technique to find k-th element |
Harder (Do These After)
| Problem | New Concept |
|---|---|
| Polynomial Queries (CSES 1736) | Lazy propagation with arithmetic sequences |
| Range Queries and Copies (CSES 1737) | Persistent segment tree |
Key Takeaways
- The Core Idea: Use segment tree maximum to quickly check if a valid position exists, then descend preferring left to find the leftmost one.
- Time Optimization: From O(n) per query to O(log n) by using tree structure for both searching and updating.
- Space Trade-off: O(n) extra space for the segment tree enables logarithmic operations.
- Pattern: “Find first position with condition” problems often use segment tree descent.
Practice Checklist
Before moving on, make sure you can:
- Build a segment tree with max in O(n) time
- Implement the descent to find leftmost position with value >= X
- Correctly update a leaf and propagate changes upward
- Handle the “no valid position” case
- Implement in under 15 minutes without reference