Booking Concert Tickets in Groups [using BIT and Segment Tree]
Do not miss this exclusive book on Binary Tree Problems. Get it now for free.
In this article, we have solved the problem of Booking Concert Tickets in Groups. This involve the concept of Binary Indexed Tree and Segment Tree.
Pre-requisites
Problem statement
People want to book tickets to a concert in groups of k people.
The hall has n rows and m seats in each row. There are two types of groups : gather and scatter
They all prefer to be together while seating.
- Gather groups: They will only book if all the k members gets seats together ie, next to each other in the same row, and if one of them doesn't get it the whole group doesn't book the tickets. If they get allotted then return the co-ordinate of the starting seat for the group.
- Scatter groups: People in these groups have to be scattered in the hall, and they will only book if all of them receives a seat. So if all of them gets a seat then return true, else return false.
In both cases whole group will only book the tickets if all the members in the group can get a row number less than or equal to maxRow value. The maxRow value is different for each group. If there are multiple options available then the one with smallest row or column will be chosen.
Example
Input
["BookMyShow", "gather", "gather", "scatter", "scatter"]
[[2, 5], [4, 0], [2, 0], [5, 1], [5, 1]]
Output
[null, [0, 0], [], true, false]
Explanation
BookMyShow bms = new BookMyShow(2, 5); // There are 2 rows with 5 seats each
bms.gather(4, 0); // return [0, 0]
// The group books seats [0, 3] of row 0.
bms.gather(2, 0); // return []
// There is only 1 seat left in row 0,
// so it is not possible to book 2 consecutive seats.
bms.scatter(5, 1); // return True
// The group books seat 4 of row 0 and seats [0, 3] of row 1.
bms.scatter(5, 1); // return False
// There is only one seat left in the hall.
Observation
- There are two group of people
- For each function call we have to allocate seats for
knumber of people and wont book the seats if any of their seat is in the row grater thanmaxRow - We have to allocate seat with minimum row and column value in case multiple choices are there.
Approach 1
Here we will discuss how to solve this problem using binary indexed tree (BIT). BIT is popularly known as fenwick tree.
Intuitive steps
Scatter
- Calculate number of available seats till maxRow.
- Return false if allotted seats + k > total seats
- Else iterate through each row and allocate seats until k = 0 starting from start.
- When k = 0 the function updates the start and returns true.
Gather
- Check if k seats are available till
maxRow - start iteration from
itomaxRow - In each iteration check if
kseats are there or not - If yes, do necessary updates and return the
{i, g[i] - k} - Else return
{}outside the loop
Explained
-
First inside the class we will create an array
btwhich will represent the BST for our program, then we will initialize array g (which is responsible for storing the seats which is already allotted in each row), variables liken(rows of seats present),m(columns of seats present),start(row number from which the next group will be allotted). Now we will have a constructor for the class which takes inn,mand initializes thenandmmember variables with their respective values, and does nothing else. -
Scatter
- When the scatter function is called the group size
kand themaxRowvalue is passed to it. So we will first use themaxRowandprefix_sum()function to get the total number of seats that got alloted. - Inside
prefix_sumit initializessum = 0and takes in a valuei, then it enters a loop untili>0. In each iterationi & (-i)computes the bitwise AND ofiwith its two's complement. This will isolate the least significant bit ofi. Then the result will be subtracted fromi. In each looping the value ofbt[i]is added to thesumvariable. Thesumis returned when the loop breaks. - The obtained value
sumis stored inside the total variable, which is the total number of seats booked until themaxRow. Once we get the total seats allotted we will check iftotal + k > ((long long)maxRow + 1) * m, if its a yes then it means that there are no adequate number of seats available in the hall and this returns false. If its not then the execution continues. - Now we will start our iteration from the start (equals to 0 in the starting) variable and then books all
kseats required. The loop continues till theivalue is less than or equal tomaxRow. - In each iteration first we will check if any seats are empty in the current row or not.
m-g[i]will give the number of seats available in that particular row, if no seats are available we will update our start toi+1and continue our loop. - If seats are available then we initializes the number of seats that can be allotted to
min(m-g[i], k)and then we will reducekby take and take number of people got allotted. Now we will call theadd()and pass in the i and k. - In the add function we will take the
i(represents the index), andval(the value that needed to be added). To update the BIT array, we start from the given indexiand add val to the element at that index. The iteration will be in such a way that we will update the elements in the BIT array that represent the parent nodes of the given index. We do this by adding the same valuevalto the elements at the index which are obtained by flipping the rightmost set bit ofi(obtained by performingi & (-i))and adding it toi. This operation is repeated until we reach the end of the BIT array. - In the end after the loop we return
true.
- When the scatter function is called the group size
-
Gather
- Like in scatter function here also we will first calculate the total seats allotted and check if
kamount of seats is available in the hall untilmaxRowor not. If its not available we will return and empty list{}. If seats are available then the program continues. - We will start our iteration from
itillmaxRowto find the row withkseats to accommodate the group. In each iteration we will check ifkseats are available or not, if its not thecontinuestatement is executed. Else we will addktog[i]and then update thebtarray usingadd(). Then we will return the indexes of the first seat for the group which would be{i, g[i] - k}. If we don't find such a case then an empty array is returned outside the loop.
- Like in scatter function here also we will first calculate the total seats allotted and check if
Code:
#include <iostream>
#include <vector>
using namespace std;
class BookMyShow {
public:
constexpr int static max_n = 50001;
long long bt[max_n + 1] = {};
long long prefix_sum(int i)
{
long long sum = 0;
for (i = i + 1; i > 0; i -= i & (-i))
sum += bt[i];
return sum;
}
void add(int i, int val)
{
for (i = i + 1; i <= max_n; i += i & (-i))
bt[i] += val;
}
int g[50001] = {}, n = 0, m = 0, start = 0;
BookMyShow(int n, int m) : n(n), m(m) { }
vector<int> gather(int k, int maxRow) {
long long total = prefix_sum(maxRow);
if (total + k > ((long long)maxRow + 1) * m)
return {};
for (int i = start; i <= maxRow; ++i) {
if (g[i] + k > m)
continue;
g[i] += k;
add(i, k);
return {i, g[i] - k};
}
return {};
}
bool scatter(int k, int maxRow) {
long long total = prefix_sum(maxRow);
if (total + k > ((long long)maxRow + 1) * m)
return false;
for (int i = start; k && i <= maxRow; ++i) {
if (m - g[i]) {
int take = min(m - g[i], k);
k -= take;
add(i, take);
g[i] += take;
}
else
start = i + 1;
}
return true;
}
};
/**
* Your BookMyShow object will be instantiated and called as such:
* BookMyShow* obj = new BookMyShow(n, m);
* vector<int> param_1 = obj->gather(k,maxRow);
* bool param_2 = obj->scatter(k,maxRow);
*/
int main() {
int n, m, choice, k, rowMax;
cout << "Enter number of rows: ";
cin >> n;
cout << "Enter number of seats per row: ";
cin >> m;
BookMyShow bms(n, m);
bool success = false;
vector<int> seat;
do {
cout << "\n\n1. Scatter" << endl;
cout << "2. Gather" << endl;
cout << "3. Quit" << endl;
cout << "Enter choice: ";
cin >> choice;
switch (choice) {
case 1:
cout << "Enter k: ";
cin >> k;
cout << "Enter rowMax: ";
cin >> rowMax;
success = bms.scatter(k, rowMax);
if (success) {
cout << "Scatter successful!" << endl;
} else {
cout << "Scatter failed: not enough seats available!" << endl;
}
break;
case 2:
cout << "Enter k: ";
cin >> k;
cout << "Enter rowMax: ";
cin >> rowMax;
seat = bms.gather(k, rowMax);
if (seat.empty()) {
cout << "Gather failed: not enough seats available!" << endl;
} else {
cout << "Gather successful! Seat " << seat[0] << "-" << seat[1] << " reserved." << endl;
}
break;
case 3:
cout << "Exiting program..." << endl;
break;
default:
cout << "Invalid choice! Please try again." << endl;
break;
}
} while (choice != 3);
return 0;
}
Output:
Enter number of rows: 2
Enter number of seats per row: 5
1. Scatter
2. Gather
3. Quit
Enter choice: 2
Enter k: 4
Enter rowMax: 0
Gather successful! Seat 0-0 reserved.
1. Scatter
2. Gather
3. Quit
Enter choice: 2
Enter k: 2
Enter rowMax: 0
Gather failed: not enough seats available!
1. Scatter
2. Gather
3. Quit
Enter choice: 1
Enter k: 5
Enter rowMax: 1
Scatter successful!
1. Scatter
2. Gather
3. Quit
Enter choice: 1
Enter k: 5
Enter rowMax: 1
Scatter failed: not enough seats available!
1. Scatter
2. Gather
3. Quit
Enter choice: 3
Exiting program...
Complexities
In BIT while performing prefix sum and update operation we start from the index i and move towards the root of the tree by using the property of binary representation. At each step we add the value stored at the current node to the sum and move to the parent node. This process is continued till we reach the root of the tree. Since the height of the tree will be log n the time complexity will also be log n. To make this operation to be feasible the space complexity will be O(n+1), as the size of the array is n+1.
Both gather() and scatter() function uses fenwick tree to calculate the prefix sum and for updating. Hence the time complexity of both these functions will be O(n log m). So the time complexity of the whole program will be O(n log m).
Both these functions use BIT for calculation hence the space complexity of the program will be O(n+1).
Approach 2
Here we will be using segment tree to solve this question
Intuitive steps
- Initialize a segment tree
- Add a function to perform range sum query
- Gather function
- Obtain index of first row which at least have
kavailable seats - If its not there return
{} - If its there
g[i]is updated tog[i]+k - Then update the segment tree
- Return
{i,g[i] - k}the index of the start seat
- Obtain index of first row which at least have
- Scatter function
- Check if k number of seats are available until
maxRow - If it is return
false - Iterate from start till
maxRow - Check if seats are available for each row
- If its there, update
g[i]and the segment tree - else update the start with
i+1 - When the loop breaks return
true
- Check if k number of seats are available until
Explained
-
First we will initialize a segment tree
st, then an arrayg(which represents the number of booked seats in each row),n(rows in the hall),m(columns in the hall),start(represents the row number from which the assignment of the seats should start) and a constructor which assignn,mand additionally the segment treestis initialized as a vector of pairs with size n*4, and each pair is initialized with the values{0, m}. This sets the initial sum of each range in the tree to 0 and maximum value in each range tom, as all rows are initially empty somseats are available. -
We need a function which calculates the sum of values in the given range of indexes of the tree. For this we will create a query function. The function takes five arguments:
tl,tr,l,r, andp(defaulted to 1). These arguments represent the current node's left and right index, the query's left and right indexes, and the current node's index in the segment tree, respectively. -
Now inside the query function we will check if
l>rif it is the range query is invalid and we will return0. Now we will check iflandrare equal totlandtrrespectively. If yes it means that the current node is representing the leaf node in the segment tree and the value can be returned directly. These were the base cases, if none are triggered we continue in our execution. -
Since the base cases were not triggered the function calculates the mid point
tmof the current nodes range using(tl+tr)/2. Now we will call the function recursively twice with updated arguments. The first call (tl,tm,l,min(r, tm), p _ 2)handles the left of the current nodes range and second call(tm + 1, tr, max(l, tm + 1), r, p _ 2 + 1)handles the right of the current nodes range. Then it returns the sum of the two recursive calls, which represents the sum of the query range within the current node's range. -
Now, we need a function which could give us the index of the first element in the segment tree whose value is greater than or equal to the given value x. So we will create the function
get_first_idx. The function takes four arguments:lv,rv,x, andp.lvandrvspecify the range of indexes in the segment tree thatprepresents.xis the value we want to find the first index greater than or equal to.pis the index of the current node in the segment tree. The default value of p is 1, which represents the root node of the segment tree. -
Inside the function we will check if the current node value is less than x, if it is that means that the subtree wont have an element greater than that, hence return
-1to indicate that. If its not then we will check if it is the leaf node or not(lv == rv), if it is then that is the first element greater than or equal to x, so we will returnlv. If its not the leaf node then we calculate the the midpoint of the rangelv,lrthen recursively search either the leftotthe right subtree depending on the value of the left child node. If its greater then search left else the right. The mid value will be updated for each recursive call. The moment we find an element greater than or equal toxwe will return its index, else we will continue our search in right sub tree. If we reach the leaf node in right sub tree then we will return-1, as there will be no element greater thanx. -
After each change we need to update the change, so for that we will create another function called
update(). The update function is called withtlandtrrepresenting the left and right bounds of the current segment of the segment tree,posrepresenting the position of the element being updated, new_val representing the new value of the element being updated, andprepresenting the current node of the segment tree. Now we will check if the left and right bounds of the current segment are equal. If yes, the current node is a leaf node and we have to update it with new value and the difference betweenmand the new value, asmis the total available seats and we do this step to keep a track of it. -
If the left and right bounds of the current segment are not equal then we need to update the child nodes of that node, and for that we will calculate the mid
tm. If theposvalue is less than or equal to the middle point, then we will update the left child of the current node by recursively calling the update function with left segment boundstlandtm. If its greater then we will do the same for the right sub tree. -
After updating the child nodes, we will update the current node by setting its value to the sum of the values of its child nodes and maximum of the differences between
mand its child nodes. Its done to keep a track on the available tickets in any row. -
Gather function
- Here we will first call the
get_first_idx()to find the index of the first row in the hall layout whose number of available seats is greater than or equal tok. If it returns a negative index or if it returns an index greater thanmaxRowthen the function returns an empty vector. - If a row is present then we will update the
g[i]. Then we will update the segment tree using the update function. - In the end it returns the first index of the seat which got reserved
{i, g[i]-k}.
- Here we will first call the
-
Scatter function
- First we will check if the k is exceeding the capacity of the hall or not. We do it by comparing
total tickets sold + k with the capacity of the hall. If it exceeds we will returnfalse. - Now we will start our loop from
starttomaxRow. For each row we will check if there are any seats available. - If its there the function calculates the number of tickets that can be sold in that row by taking the minimum value between
kandm-g[i]. After this we will update the number of tickets sold in the arrayg, by adding the previously calculated value. Then we will update the segment tree by calling the update function. - In the end after ending the loop, it returns
true.
- First we will check if the k is exceeding the capacity of the hall or not. We do it by comparing
Code
#include <iostream>
#include <vector>
using namespace std;
class BookMyShow {
public:
vector<pair<long long, int>> st;
int g[50001] = {}, n = 0, m = 0, start = 0;
long long query(int tl, int tr, int l, int r, int p = 1) {
if (l > r)
return 0;
if (l == tl && r == tr)
return st[p].first;
int tm = (tl + tr) / 2;
return query(tl, tm, l, min(r, tm), p * 2) + query(tm + 1, tr, max(l, tm + 1), r, p * 2 + 1);
}
int get_first_idx(int lv, int rv, int x, int p = 1) {
if (st[p].second < x)
return -1;
while (lv != rv) {
int mid = lv + (rv - lv) / 2;
if (st[2 * p].second >= x) {
p *= 2;
rv = mid;
} else {
p = 2 * p + 1;
lv = mid + 1;
}
}
return lv;
}
void update(int tl, int tr, int pos, int new_val, int p = 1) {
if (tl == tr)
st[p] = {new_val, m - new_val};
else {
int tm = (tl + tr) / 2;
if (pos <= tm)
update(tl, tm, pos, new_val, p * 2);
else
update(tm + 1, tr, pos, new_val, p * 2 + 1);
st[p] = {st[p * 2].first + st[p * 2 + 1].first, max(st[p * 2].second, st[p * 2 + 1].second)};
}
}
BookMyShow(int n, int m) : n(n), m(m) {
st = vector<pair<long long, int>>(n * 4, {0, m});
}
vector<int> gather(int k, int maxRow) {
int i = get_first_idx(0, n - 1, k);
if (i < 0 || i > maxRow)
return {};
g[i] += k;
update(0, n - 1, i, g[i]);
return {i, g[i] - k};
}
bool scatter(int k, int maxRow) {
if (query(0, n - 1, 0, maxRow) + k > ((long long)maxRow + 1) * m)
return false;
for (int i = start; k && i <= maxRow; ++i) {
if (m - g[i]) {
int take = min(m - g[i], k);
k -= take;
g[i] += take;
update(0, n - 1, i, g[i]);
}
else
start = i + 1;
}
return true;
}
};
int main() {
int n, m, choice, k, rowMax;
cout << "Enter number of rows: ";
cin >> n;
cout << "Enter number of seats per row: ";
cin >> m;
BookMyShow bms(n, m);
bool success = false;
vector<int> seat;
do {
cout << "\n\n1. Scatter" << endl;
cout << "2. Gather" << endl;
cout << "3. Quit" << endl;
cout << "Enter choice: ";
cin >> choice;
switch (choice) {
case 1:
cout << "Enter k: ";
cin >> k;
cout << "Enter rowMax: ";
cin >> rowMax;
success = bms.scatter(k, rowMax);
if (success) {
cout << "Scatter successful!" << endl;
} else {
cout << "Scatter failed: not enough seats available!" << endl;
}
break;
case 2:
cout << "Enter k: ";
cin >> k;
cout << "Enter rowMax: ";
cin >> rowMax;
seat = bms.gather(k, rowMax);
if (seat.empty()) {
cout << "Gather failed: not enough seats available!" << endl;
} else {
cout << "Gather successful! Seat " << seat[0] << "-" << seat[1] << " reserved." << endl;
}
break;
case 3:
cout << "Exiting program..." << endl;
break;
default:
cout << "Invalid choice! Please try again." << endl;
break;
}
} while (choice != 3);
return 0;
}
Complexity Analysis
Complexity mainly depends on the query(), update() and get_first_idx().
get_first_idx()function: The time complexity of theget_first_idx()function isO(log n)wherenis the number of elements in the segment tree. This is because the function performs a binary search on the segment tree to find the first index whose value is greater than or equal to the given valuex.update()function: The time complexity of theupdate()function is alsoO(log n)wherenis the number of elements in the segment tree. This is because the function updates the value of a node in the segment tree by traversing down from the root to the leaf node.query()function: The time complexity of thequery()function isO(log n)wherenis the number of elements in the segment tree. This is because the function performs a binary search on the segment tree to find the sum of values within a range.
Inside the scatter(), we do use a loop which runs for maxRow number of times. Hence we can say that the total time complexity of the program would be O(log n + maxRow).
The space complexity of this solution is O(N log N), where N is the total number of seats in the theater. This is because we are creating a segment tree with 4*N nodes, where N is the number of seats in the theater, and the height of the tree is log N. Each node in the segment tree requires a pair of values, so the total space required is O(N log N).
Output
Enter number of rows: 2
Enter number of seats per row: 5
1. Scatter
2. Gather
3. Quit
Enter choice: 2
Enter k: 4
Enter rowMax: 0
Gather successful! Seat 0-0 reserved.
1. Scatter
2. Gather
3. Quit
Enter choice: 2
Enter k: 2
Enter rowMax: 0
Gather failed: not enough seats available!
1. Scatter
2. Gather
3. Quit
Enter choice: 1
Enter k: 5
Enter rowMax: 1
Scatter successful!
1. Scatter
2. Gather
3. Quit
Enter choice: 1
Enter k: 5
Enter rowMax: 1
Scatter failed: not enough seats available!
1. Scatter
2. Gather
3. Quit
Enter choice: 3
Exiting program...
With this article at OpenGenus, you must have the complete idea of solving this problem of booking concert tickets.
Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.