给定一个长度为 nn,且非严格递增的序列 AA。
再给定 qq 组查询,每组查询为:
1 l r x
:输出 Al∼rAl∼r 中等于 xx 最左边的数的下标,若不存在输出 -1
。
2 l r x
:输出 Al∼rAl∼r 中等于 xx 最右边的数的下标,若不存在输出 -1
。
3 l r x
:输出 Al∼rAl∼r 中大于等于 xx 的第一个数的下标,若不存在输出 -1
。
4 l r x
:输出 Al∼rAl∼r 中大于 xx 的第一个数的下标,若不存在输出 -1
。
第一行输入两个正整数 n,qn,q。(1≤n,q≤105)(1≤n,q≤105)
第二行输出 nn 个整数 AiAi。(1≤Ai≤105,1≤i≤105,1≤q≤105)(1≤Ai≤105,1≤i≤105,1≤q≤105)
接下来 qq 行输入,表示查询,具体为:
1 l r x
:输出 Al∼rAl∼r 中等于 xx 最左边的数的下标,若不存在输出 -1
。
2 l r x
:输出 Al∼rAl∼r 中等于 xx 最右边的数的下标,若不存在输出 -1
。
3 l r x
:输出 Al∼rAl∼r 中大于等于 xx 的第一个数的下标,若不存在输出 -1
。
4 l r x
:输出 Al∼rAl∼r 中大于 xx 的第一个数的下标,若不存在输出 -1
。
1≤x≤105,1≤l≤r≤n1≤x≤105,1≤l≤r≤n。
对于每组查询,输出一个整数,为按照题目要求查询的结果。
6 6
1 2 2 2 3 4
1 2 4 2
2 2 4 2
3 2 4 2
3 1 1 2
4 2 4 2
4 2 5 2
2
4
2
-1
-1
5
语言 | 最大运行时间 | 最大运行内存 |
---|---|---|
C++ | 1s | 256M |
C | 1s | 256M |
Java | 2s | 256M |
Python3 | 3s | 256M |
PyPy3 | 3s | 256M |
Go | 3s | 256M |
JavaScript | 3s | 256M |
总通过次数: 1271 | 总提交次数: 1421 | 通过率: 89.4%
难度: 中等 标签: 二分
我们需要在一个非严格递增序列上高效处理四种区间查询操作。序列长度和查询次数均达到 105,因此需要 O(logn) 时间复杂度的查询算法。二分查找是理想选择,但需针对不同查询类型进行变体实现。
#include
#include
using namespace std;
const int MAXN = 100010;
int a[MAXN];
// 在闭区间[l, r]查找第一个≥x的位置
int my_lower_bound(int l, int r, int x) {
int low = l, high = r;
int ans = r + 1; // 初始化为区间外
while (low <= high) {
int mid = low + (high - low) / 2; // 防溢出
if (a[mid] >= x) {
ans = mid;
high = mid - 1; // 向左继续查找
} else {
low = mid + 1; // 向右继续查找
}
}
return ans;
}
// 在闭区间[l, r]查找第一个>x的位置
int my_upper_bound(int l, int r, int x) {
int low = l, high = r;
int ans = r + 1; // 初始化为区间外
while (low <= high) {
int mid = low + (high - low) / 2; // 防溢出
if (a[mid] > x) {
ans = mid;
high = mid - 1; // 向左继续查找
} else {
low = mid + 1; // 向右继续查找
}
}
return ans;
}
int main() {
int n, q;
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
while (q--) {
int op, l, r, x;
scanf("%d%d%d%d", &op, &l, &r, &x);
if (op == 1) { // 等于x的最左位置
int pos = my_lower_bound(l, r, x);
if (pos <= r && a[pos] == x) {
printf("%d\n", pos);
} else {
printf("-1\n");
}
} else if (op == 2) { // 等于x的最右位置
int pos = my_upper_bound(l, r, x);
if (pos - 1 >= l && a[pos - 1] == x) {
printf("%d\n", pos - 1);
} else {
printf("-1\n");
}
} else if (op == 3) { // ≥x的第一个位置
int pos = my_lower_bound(l, r, x);
if (pos <= r) {
printf("%d\n", pos);
} else {
printf("-1\n");
}
} else if (op == 4) { // >x的第一个位置
int pos = my_upper_bound(l, r, x);
if (pos <= r) {
printf("%d\n", pos);
} else {
printf("-1\n");
}
}
}
return 0;
}
mid = low + (high - low) / 2
避免 (low + high)
溢出ans = r + 1
表示未找到时的默认值low <= high
确保完全覆盖闭区间查询类型 | 核心操作 | 验证条件 |
---|---|---|
1 | my_lower_bound() |
位置值等于x且位置≤r |
2 | my_upper_bound() - 1 |
前一位值等于x且位置-1≥l |
3 | my_lower_bound() |
位置≤r(存在≥x的数) |
4 | my_upper_bound() |
位置≤r(存在>x的数) |
a[1]
到a[n]
)6 6
1 2 2 2 3 4
1 2 4 2
2 2 4 2
3 2 4 2
3 1 1 2
4 2 4 2
4 2 5 2
查询1:在[2,4]找等于2的最左位置
→ my_lower_bound(2,4,2)=2
(a[2]=2)→ 输出2 ✓
查询2:在[2,4]找等于2的最右位置
→ my_upper_bound(2,4,2)=5
→ 5-1=4(a[4]=2)→ 输出4 ✓
查询3:在[2,4]找≥2的第一个位置
→ my_lower_bound(2,4,2)=2
→ 输出2 ✓
查询3:在[1,1]找≥2的第一个位置
→ my_lower_bound(1,1,2)=2
(>1)→ 输出-1 ✓
查询4:在[2,4]找>2的第一个位置
→ my_upper_bound(2,4,2)=5
(>4)→ 输出-1 ✓
查询4:在[2,5]找>2的第一个位置
→ my_upper_bound(2,5,2)=5
(a[5]=3>2)→ 输出5 ✓
测试点 | 输入数据 | 预期输出 | 说明 |
---|---|---|---|
单元素存在 | 1 1 5 → 查询1 1 1 5 |
1 | 唯一元素满足条件 |
单元素不存在 | 1 1 5 → 查询1 1 1 3 |
-1 | 唯一元素不满足条件 |
左边界匹配 | 5 10 10 10 20 30 → 查询1 1 5 10 |
1 | 第一个元素即匹配 |
右边界匹配 | 5 10 20 30 30 30 → 查询2 1 5 30 |
5 | 最后一个元素匹配 |
测试点 | 输入数据 | 预期输出 | 说明 |
---|---|---|---|
全相同序列 | 5 1 1 1 1 1 → 查询2 1 5 1 |
5 | 所有元素都匹配 |
无目标值 | 5 10 20 30 40 50 → 查询3 1 5 60 |
-1 | 所有元素小于目标值 |
跨越多个区间 | 7 1 2 2 3 3 3 4 → 查询4 2 6 2 |
4 | 从重复区间中找第一个大于 |
数组越界防护
pos-1
需≥l)重复元素处理
my_upper_bound-1
定位整数溢出预防
mid = low + (high - low)/2
而非(low+high)/2
输入输出效率
scanf/printf
代替cin/cout
(速度差5倍以上)ios::sync_with_stdio(false)
预计算优化
// 对每种值存储所有出现位置
vector pos_map[MAX_VAL];
// 预处理
for (int i = 1; i <= n; i++)
pos_map[a[i]].push_back(i);
// 查询时在对应值的数组中二分查找区间
auto it = lower_bound(pos_map[x].begin(), pos_map[x].end(), l);
if (it != pos_map[x].end() && *it <= r)
return *it; // 找到
批量查询处理
struct Query { int op, l, r, x, id; };
vector queries;
// 按x排序查询后批量处理
sort(queries.begin(), queries.end(), [](auto& a, auto& b){
return a.x < b.x;
});
尾递归改写
// 将递归二分改为迭代(示例)
while (low <= high) {
int mid = low + (high - low) / 2;
if (check(mid)) {
ans = mid;
high = mid - 1; // 或 low = mid + 1
} else {
// 调整边界
}
}
通过精心设计的二分查找变体,我们高效解决了非严格递增序列上的四类区间查询问题。关键点在于:
此方案在 O(n+qlogn) 时间复杂度内解决问题,完全满足题目约束条件,且能通过所有边界测试。