题解:洛谷 P2249 查找
【题目来源】
【题目描述】
输入 \(n\) 个不超过 \(10^9\) 的单调不减的(就是后面的数字不小于前面的数字)非负整数 \(a_1,a_2,\dots,a_n\),然后进行 \(m\) 次询问。对于每次询问,给出一个整数 \(q\),要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 \(−1\)。
【输入】
第一行 \(2\) 个整数 \(n\) 和 \(m\),表示数字个数和询问次数。
第二行 \(n\) 个整数,表示这些待查询的数字。
第三行 \(m\) 个整数,表示询问这些数字的编号,从 \(1\) 开始编号。
【输出】
输出一行,\(m\) 个整数,以空格隔开,表示答案。
【输入样例】
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
【输出样例】
1 2 -1
【核心思想】
-
问题分析:给定一个长度为 \(n\) 的单调不减(非降序)整数序列,进行 \(m\) 次询问,每次询问一个整数 \(q\),要求输出 \(q\) 在序列中第一次出现的位置(从 \(1\) 开始编号),若不存在则输出 \(-1\)。由于序列有序且询问较多,需要利用单调性进行快速查找,本质是二分查找的经典应用。
-
算法选择:
- 整数二分查找:利用序列的非降序性质,通过不断缩小搜索区间来定位目标。对于每次查询,采用“左闭右闭”或“左闭右开”的二分模板,找到第一个 \(\ge q\) 的位置,然后检查该位置的值是否等于 \(q\)。
- C++ STL 的
lower_bound(备选):可直接使用lower_bound找到第一个不小于 \(q\) 的迭代器,再判断其值。但为练习二分,手写实现更佳。
-
关键步骤:
- 读入与存储:读取 \(n\)、\(m\),以及非降序数组 \(a[1..n]\)。
- 二分查找函数
find(q):- 初始化左边界 \(l=1\),右边界 \(r=n\)(闭区间 \([l, r]\))。
- 当 \(l < r\) 时,计算 \(mid = \lfloor (l+r)/2 \rfloor\)。
- 若 \(a[mid] \ge q\),则第一个 \(\ge q\) 的位置可能在 \([l, mid]\) 中,令 \(r = mid\)。
- 否则,\(a[mid] < q\),则应在 \([mid+1, r]\) 中查找,令 \(l = mid+1\)。
- 循环结束后,\(l\) 即为第一个 \(\ge q\) 的位置。
- 若 \(a[l] == q\),返回 \(l\);否则返回 \(-1\)。
- 处理询问:对每个查询 \(q\),调用
find(q)并输出结果,用空格分隔。
-
时间/空间复杂度:
- 时间复杂度:\(O((n+m)\log n)\),每次二分 \(O(\log n)\),共 \(m\) 次查询。
- 空间复杂度:\(O(n)\),存储数组。
-
二分查找的核心思想:
- 单调性利用:由于数组非降序,判断 \(a[mid]\) 与 \(q\) 的关系即可确定目标在左半还是右半,每次排除一半元素。
- 重复元素处理:通过条件 \(a[mid] \ge q\) 将右边界 \(r\) 左移,从而找到第一个 \(\ge q\) 的位置,确保即使有重复元素也能定位到首次出现。
- 边界条件:循环结束后,\(l\) 指向第一个 \(\ge q\) 的位置,但需验证该位置的值是否等于 \(q\),否则说明 \(q\) 不存在。
- 适用于有序序列的快速查找问题,是算法竞赛中最基础的二分模板。
【解题思路】

【算法标签】
普及- #整数二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 全局变量:
// n: 数组长度
// m: 查询次数
// q: 当前查询值
// tmp: 临时变量(未使用)
// a[1000005]: 存储有序数组
int n, m, q, tmp;
int a[1000005];
/**
* 二分查找函数
* @param q 要查找的值
* @return 值在数组中的位置(从1开始),未找到返回-1
*/
int find(int q)
{
int l = 1, r = n; // 初始化左右边界
while (l < r)
{
int mid = l + (r - l) / 2; // 计算中间位置,防止溢出
// 调整搜索范围
if (a[mid] >= q)
{
r = mid; // 搜索左半部分
}
else
{
l = mid + 1; // 搜索右半部分
}
}
// 检查是否找到目标值
if (a[l] == q)
{
return l;
}
else
{
return -1;
}
}
int main()
{
// 输入数组长度和查询次数
cin >> n >> m;
// 输入有序数组
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 处理每个查询
for (int i = 1; i <= m; i++)
{
cin >> q; // 输入查询值
cout << find(q) << " "; // 输出查找结果
}
return 0;
}
// 使用lower_bound再做一遍
#include <bits/stdc++.h>
using namespace std;
// 全局变量:
// n: 数组长度
// m: 查询次数
// q: 当前查询值
// mark: 存储查找结果的位置
// a[1000005]: 存储有序数组
int n, m, q, mark;
int a[1000005];
int main()
{
// 输入数组长度和查询次数
cin >> n >> m;
// 输入有序数组(从索引1开始存储)
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 处理每个查询
for (int i = 1; i <= m; i++)
{
cin >> q; // 输入查询值
// 使用lower_bound查找第一个不小于q的元素位置
mark = lower_bound(a + 1, a + n + 1, q) - a;
// 检查是否找到确切值
if (a[mark] == q)
{
cout << mark << " "; // 输出找到的位置
}
else
{
cout << "-1 "; // 未找到输出-1
}
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int n, m, q, tmp;
int a[1000005];
// 二分查找函数,查找目标值q
int find(int q)
{
int l = 0, r = n + 1; // 开区间 (0, n+1),实际有效数据是a[1]到a[n]
// 二分查找循环
while (l + 1 < r) // 当区间长度大于1时继续
{
int mid = (l + r) / 2; // 计算中间位置
if (a[mid] >= q) // 如果中间值大于等于目标值
{
r = mid; // 向左收缩,r指向可能的第一个≥q的位置
}
else
{
l = mid; // 向右收缩
}
}
// 循环结束时,l是最后一个小于q的位置,r是第一个大于等于q的位置
if (a[r] == q) // 检查找到的位置是否等于q
{
return r; // 返回下标
}
else
{
return -1; // 未找到
}
}
int main()
{
cin >> n >> m; // 输入数组长度n和查询次数m
// 输入数组,从1开始存储
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 处理m个查询
for (int i = 1; i <= m; i++)
{
cin >> q; // 输入要查找的值
cout << find(q) << " "; // 输出查找结果
}
return 0;
}
【运行结果】
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
1 2 -1
浙公网安备 33010602011771号