The "Hopper Company Queries III" problem describes a scenario where you are given a set of n
companies, each with a certain value. You are required to efficiently process q
queries of two types:
Key constraints:
The first idea that comes to mind is to use a simple array to represent the companies and, for each query, scan the relevant range to find the maximum. However, this brute-force approach would take O(n) time for each range query, which is too slow for large inputs.
To optimize, we want a data structure that supports both point updates and range maximum queries in logarithmic time. Segment Trees and Binary Indexed Trees (Fenwick Trees) are classic solutions for this type of problem. Since we need to support range maximum (not sum), a Segment Tree is better suited.
By building a Segment Tree, we can:
We'll use a Segment Tree to efficiently handle both update and range maximum queries. Here's how we approach the problem step-by-step:
i
, update the corresponding leaf node in the Segment Tree.This approach ensures both types of queries are handled quickly, even for large datasets.
Let's walk through an example:
Input:
At each step, both updates and queries are handled efficiently, with no need to scan the array directly.
Brute-Force Approach:
The Segment Tree's logarithmic time for updates and queries is the key to handling large numbers of operations efficiently.
In summary, the "Hopper Company Queries III" problem is a classic example of efficiently handling dynamic range queries and updates. By using a Segment Tree, we achieve fast O(log n) time for both update and range maximum queries, making the solution scalable for large inputs. The key insight is choosing the right data structure for the job, and understanding how to implement and use it effectively. This approach is elegant because it balances both types of operations, ensuring the system remains efficient no matter how the queries are interleaved.
class SegmentTree:
def __init__(self, arr):
n = len(arr)
self.n = n
self.tree = [0] * (4 * n)
self.build(arr, 0, 0, n-1)
def build(self, arr, node, l, r):
if l == r:
self.tree[node] = arr[l]
else:
mid = (l + r) // 2
self.build(arr, 2*node+1, l, mid)
self.build(arr, 2*node+2, mid+1, r)
self.tree[node] = max(self.tree[2*node+1], self.tree[2*node+2])
def update(self, idx, value, node=0, l=0, r=None):
if r is None:
r = self.n - 1
if l == r:
self.tree[node] = value
else:
mid = (l + r) // 2
if idx <= mid:
self.update(idx, value, 2*node+1, l, mid)
else:
self.update(idx, value, 2*node+2, mid+1, r)
self.tree[node] = max(self.tree[2*node+1], self.tree[2*node+2])
def query(self, ql, qr, node=0, l=0, r=None):
if r is None:
r = self.n - 1
if qr < l or ql > r:
return float('-inf')
if ql <= l and r <= qr:
return self.tree[node]
mid = (l + r) // 2
left = self.query(ql, qr, 2*node+1, l, mid)
right = self.query(ql, qr, 2*node+2, mid+1, r)
return max(left, right)
# Example usage:
# arr = [5, 3, 8, 6, 2]
# st = SegmentTree(arr)
# st.update(2, 10) # Update index 2 to 10
# print(st.query(1, 3)) # Query max from index 1 to 3
# st.update(4, 7)
# print(st.query(0, 4))
#include <vector>
#include <algorithm>
using namespace std;
class SegmentTree {
vector<int> tree;
int n;
void build(vector<int>& arr, int node, int l, int r) {
if (l == r) {
tree[node] = arr[l];
} else {
int mid = (l + r) / 2;
build(arr, 2*node+1, l, mid);
build(arr, 2*node+2, mid+1, r);
tree[node] = max(tree[2*node+1], tree[2*node+2]);
}
}
void update(int idx, int value, int node, int l, int r) {
if (l == r) {
tree[node] = value;
} else {
int mid = (l + r) / 2;
if (idx <= mid)
update(idx, value, 2*node+1, l, mid);
else
update(idx, value, 2*node+2, mid+1, r);
tree[node] = max(tree[2*node+1], tree[2*node+2]);
}
}
int query(int ql, int qr, int node, int l, int r) {
if (qr < l || ql > r)
return INT_MIN;
if (ql <= l && r <= qr)
return tree[node];
int mid = (l + r) / 2;
int left = query(ql, qr, 2*node+1, l, mid);
int right = query(ql, qr, 2*node+2, mid+1, r);
return max(left, right);
}
public:
SegmentTree(vector<int>& arr) {
n = arr.size();
tree.resize(4*n);
build(arr, 0, 0, n-1);
}
void update(int idx, int value) {
update(idx, value, 0, 0, n-1);
}
int query(int l, int r) {
return query(l, r, 0, 0, n-1);
}
};
// Example usage:
// vector<int> arr = {5, 3, 8, 6, 2};
// SegmentTree st(arr);
// st.update(2, 10);
// int res1 = st.query(1, 3);
// st.update(4, 7);
// int res2 = st.query(0, 4);
class SegmentTree {
int[] tree;
int n;
public SegmentTree(int[] arr) {
n = arr.length;
tree = new int[4 * n];
build(arr, 0, 0, n - 1);
}
private void build(int[] arr, int node, int l, int r) {
if (l == r) {
tree[node] = arr[l];
} else {
int mid = (l + r) / 2;
build(arr, 2 * node + 1, l, mid);
build(arr, 2 * node + 2, mid + 1, r);
tree[node] = Math.max(tree[2 * node + 1], tree[2 * node + 2]);
}
}
public void update(int idx, int value) {
update(idx, value, 0, 0, n - 1);
}
private void update(int idx, int value, int node, int l, int r) {
if (l == r) {
tree[node] = value;
} else {
int mid = (l + r) / 2;
if (idx <= mid)
update(idx, value, 2 * node + 1, l, mid);
else
update(idx, value, 2 * node + 2, mid + 1, r);
tree[node] = Math.max(tree[2 * node + 1], tree[2 * node + 2]);
}
}
public int query(int l, int r) {
return query(l, r, 0, 0, n - 1);
}
private int query(int ql, int qr, int node, int l, int r) {
if (qr < l || ql > r)
return Integer.MIN_VALUE;
if (ql <= l && r <= qr)
return tree[node];
int mid = (l + r) / 2;
int left = query(ql, qr, 2 * node + 1, l, mid);
int right = query(ql, qr, 2 * node + 2, mid + 1, r);
return Math.max(left, right);
}
}
// Example usage:
// int[] arr = {5, 3, 8, 6, 2};
// SegmentTree st = new SegmentTree(arr);
// st.update(2, 10);
// int res1 = st.query(1, 3);
// st.update(4, 7);
// int res2 = st.query(0, 4);
class SegmentTree {
constructor(arr) {
this.n = arr.length;
this.tree = new Array(4 * this.n).fill(0);
this.build(arr, 0, 0, this.n - 1);
}
build(arr, node, l, r) {
if (l === r) {
this.tree[node] = arr[l];
} else {
const mid = Math.floor((l + r) / 2);
this.build(arr, 2 * node + 1, l, mid);
this.build(arr, 2 * node + 2, mid + 1, r);
this.tree[node] = Math.max(this.tree[2 * node + 1], this.tree[2 * node + 2]);
}
}
update(idx, value, node = 0, l = 0, r = this.n - 1) {
if (l === r) {
this.tree[node] = value;
} else {
const mid = Math.floor((l + r) / 2);
if (idx <= mid) {
this.update(idx, value, 2 * node + 1, l, mid);
} else {
this.update(idx, value, 2 * node + 2, mid + 1, r);
}
this.tree[node] = Math.max(this.tree[2 * node + 1], this.tree[2 * node + 2]);
}
}
query(ql, qr, node = 0, l = 0, r = this.n - 1) {
if (qr < l || ql > r) {
return -Infinity;
}
if (ql <= l && r <= qr) {
return this.tree[node];
}
const mid = Math.floor((l + r) / 2);
const left = this.query(ql, qr, 2 * node + 1, l, mid);
const right = this.query(ql, qr, 2 * node + 2, mid + 1, r);
return Math.max(left, right);
}
}
// Example usage:
// let arr = [5, 3, 8, 6, 2];
// let st = new SegmentTree(arr);
// st.update(2, 10);
// console.log(st.query(1, 3));
// st.update(4, 7);
// console.log(st.query(0, 4));