<think>题目:Balanced Lineup
问题描述:有N头牛,每头牛有一个身高。有Q个查询,每个查询给出一个区间[A, B],要求输出该区间内最高牛的身高和最低牛的身高之差。
由于N最大为50,000,Q最大为200,000,我们不可能对每个查询都遍历区间(那将是O(N*Q)的复杂度,会超时)。
因此,我们需要一种高效的数据结构来支持区间最大/最小值查询。线段树(Segment Tree)或稀疏表(Sparse Table)是合适的选择。
这里我们使用线段树,因为线段树支持更新操作(虽然本题没有更新,但线段树是一个通用解法),而且空间复杂度为O(N),构建时间为O(N),每个查询时间为O(logN)。
步骤:
1. 构建线段树,每个节点存储对应区间的最大值和最小值。
2. 对于每个查询,查询区间的最大值和最小值,然后输出差值。
线段树节点设计:
struct Node {
int l, r;
int max_val, min_val;
}
构建树:递归构建,每个节点的最大值和最小值由左右子树得到。
查询:对于给定的区间[A, B],返回一个包含最大值和最小值的结构,然后计算差值。
注意:数组下标从0开始,但题目输入中的牛编号是从1开始的,我们需要调整查询区间(减1)。
由于查询只有200,000次,每次查询O(logN)总时间为O(Q logN),在可接受范围内。
代码实现:
我们将实现一个线段树,支持:
build_tree: 构建线段树
query: 查询区间的最小值和最大值
注意:线段树数组通常开4倍空间。
样例验证:
输入:N=6, Q=3, 高度=[1,7,3,4,2,5]
查询:[1,5] -> [0,4]:最大值7,最小值1 -> 6
[4,6] -> [3,5]:最大值5,最小值2 -> 3
[2,2] -> [1,1]:最大值7,最小值7 -> 0
实现:
</think>
### 解决方案:使用线段树高效查询区间最值差
#### 问题分析
给定N头牛的身高序列和Q个区间查询,每个查询要求计算区间内最高身高和最低身高的差值。由于N最大为50,000,Q最大为200,000,暴力解法(每个查询遍历区间)的时间复杂度为O(NQ)会超时。
采用线段树可以在O(N)预处理时间和O(logN)单次查询时间内解决:
1. **线段树结构**:每个节点存储区间的最小值和最大值
2. **查询处理**:对每个区间查询,返回其最小值和最大值并计算差值
#### 完整代码实现(C++)
```cpp
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;
struct Node {
int min_val;
int max_val;
};
class SegmentTree {
private:
vector<Node> tree;
vector<int> data;
int n;
void build_tree(int node, int start, int end) {
if (start == end) {
tree[node] = { data[start], data[start] };
} else {
int mid = (start + end) / 2;
build_tree(2 * node + 1, start, mid); // 左子树
build_tree(2 * node + 2, mid + 1, end); // 右子树
tree[node].min_val = min(tree[2 * node + 1].min_val, tree[2 * node + 2].min_val);
tree[node].max_val = max(tree[2 * node + 1].max_val, tree[2 * node + 2].max_val);
}
}
Node query_range(int node, int start, int end, int l, int r) {
if (r < start || end < l) {
return { INT_MAX, INT_MIN }; // 区间不重叠,返回无效值
}
if (l <= start && end <= r) {
return tree[node]; // 当前区间完全包含在查询区间内
}
int mid = (start + end) / 2;
Node left_res = query_range(2 * node + 1, start, mid, l, r);
Node right_res = query_range(2 * node + 2, mid + 1, end, l, r);
return {
min(left_res.min_val, right_res.min_val),
max(left_res.max_val, right_res.max_val)
};
}
public:
SegmentTree(const vector<int>& arr) : data(arr), n(arr.size()) {
tree.resize(4 * n);
build_tree(0, 0, n-1);
}
int query(int l, int r) {
Node res = query_range(0, 0, n-1, l, r);
return res.max_val - res.min_val;
}
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q;
cin >> N >> Q;
vector<int> heights(N);
for (int i = 0; i < N; i++) {
cin >> heights[i];
}
SegmentTree st(heights);
for (int i = 0; i < Q; i++) {
int A, B;
cin >> A >> B;
// 输入区间是从1开始,转换为0-based索引 [A-1, B-1]
cout << st.query(A-1, B-1) << '\n';
}
return 0;
}
```
#### 代码详解
1. **线段树结构**
- `Node`结构体:存储区间的最小值`min_val`和最大值`max_val`
- 树数组`tree`:大小设为4倍原数组长度(确保完全二叉树存储)
2. **建树过程(O(N))**
```cpp
void build_tree(int node, int start, int end) {
if (start == end) { // 叶子节点
tree[node] = {data[start], data[start]};
} else {
int mid = (start+end)/2;
build_tree(2*node+1, start, mid); // 递归构建左子树
build_tree(2*node+2, mid+1, end); // 递归构建右子树
// 合并子树信息
tree[node].min_val = min(left.min, right.min);
tree[node].max_val = max(left.max, right.max);
}
}
```
3. **区间查询(O(logN))**
```cpp
Node query_range(int node, int start, int end, int l, int r) {
// 当前区间与查询区间无重叠
if (r < start || end < l)
return {INT_MAX, INT_MIN};
// 当前区间完全包含在查询区间内
if (l <= start && end <= r)
return tree[node];
// 部分重叠,递归查询子树
int mid = (start+end)/2;
Node left = query_range(2*node+1, start, mid, l, r);
Node right = query_range(2*node+2, mid+1, end, l, r);
return {
min(left.min_val, right.min_val),
max(left.max_val, right.max_val)
};
}
```
4. **主流程**
- 读取牛的数量`N`和查询数量`Q`
- 读取牛的身高数据
- 构建线段树(预处理)
- 对每个查询`[A,B]`:
- 转换为0-based索引`[A-1, B-1]`
- 调用`query()`获取区间最值差并输出
#### 复杂度分析
- **时间复杂度**:
- 建树:O(N)
- 单次查询:O(logN)
- 总时间:O(N + QlogN) ≈ 50,000 + 200,000×log₂(50,000) ≈ 3.6×10⁶
- **空间复杂度**:O(N)(4倍数组空间 ≈ 200,000)
#### 优化讨论
1. **稀疏表替代方案**:
- 适用场景:静态数组(无更新操作)
- 优势:O(1)查询,O(NlogN)预处理
- 劣势:空间复杂度O(NlogN)
2. **线段树优化**:
- 非递归实现
- 内存池分配
- 查询剪枝优化
样例验证:
```text
输入:
6 3
1
7
3
4
2
5
1 5
4 6
2 2
输出:
6 // [1,5]: max=7, min=1 → 6
3 // [4,6]: max=5, min=2 → 3
0 // [2,2]: max=min=7 → 0
```