[蓝桥杯]整数查找

问题描述

给定一个长度为 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) 时间复杂度的查询算法。二分查找是理想选择,但需针对不同查询类型进行变体实现。

核心思想
  1. ​二分查找变体​​:针对四种查询设计两种核心二分查找函数:
    • ​下界查找 (my_lower_bound)​​:找到第一个 ​​大于等于​​ x 的元素位置
    • ​上界查找 (my_upper_bound)​​:找到第一个 ​​大于​​ x 的元素位置
  2. ​查询转换​​:
    • 查询1(等于x的最左位置)→ 下界查找 + 值验证
    • 查询2(等于x的最右位置)→ 上界查找的前一个位置 + 值验证
    • 查询3(≥x的第一个位置)→ 直接下界查找
    • 查询4(>x的第一个位置)→ 直接上界查找
算法演示
[蓝桥杯]整数查找_第1张图片

完整代码实现

#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;
}

代码解析与关键点

1. 二分查找实现
  • ​防溢出技巧​​:mid = low + (high - low) / 2 避免 (low + high) 溢出
  • ​初始值设置​​:ans = r + 1 表示未找到时的默认值
  • ​循环条件​​:low <= high 确保完全覆盖闭区间
2. 查询处理逻辑
查询类型 核心操作 验证条件
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的数)
3. 1-indexed设计
  • 数组存储从下标1开始(a[1]a[n]
  • 输入输出直接使用1-indexed坐标,避免转换

实例验证

样例输入
 
  
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. ​查询1​​:在[2,4]找等于2的最左位置
    → my_lower_bound(2,4,2)=2(a[2]=2)→ 输出2 ✓

  2. ​查询2​​:在[2,4]找等于2的最右位置
    → my_upper_bound(2,4,2)=5 → 5-1=4(a[4]=2)→ 输出4 ✓

  3. ​查询3​​:在[2,4]找≥2的第一个位置
    → my_lower_bound(2,4,2)=2 → 输出2 ✓

  4. ​查询3​​:在[1,1]找≥2的第一个位置
    → my_lower_bound(1,1,2)=2(>1)→ 输出-1 ✓

  5. ​查询4​​:在[2,4]找>2的第一个位置
    → my_upper_bound(2,4,2)=5(>4)→ 输出-1 ✓

  6. ​查询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 从重复区间中找第一个大于
性能测试
  • ​大数据量​​:n=q=105 的随机数据
  • ​极端分布​​:全递增序列中查询边界值

注意事项

  1. ​数组越界防护​

    • 二分查找前验证区间有效性(l ≤ r)
    • 访问数组前检查下标范围(如pos-1需≥l)
  2. ​重复元素处理​

    • 非严格递增序列中,重复元素连续出现
    • 查询2依赖此特性通过my_upper_bound-1定位
  3. ​整数溢出预防​

    • 使用mid = low + (high - low)/2而非(low+high)/2
  4. ​输入输出效率​

    • 使用scanf/printf代替cin/cout(速度差5倍以上)
    • 若用C++流,需添加ios::sync_with_stdio(false)

优化建议

  1. ​预计算优化​

     
    // 对每种值存储所有出现位置
    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; // 找到

    • ​适用场景​​:查询中x的种类较少时
    • ​复杂度​​:预处理O(n),查询O(logn)
  2. ​批量查询处理​

     
    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; 
    });

    • ​优势​​:增强内存访问局部性
    • ​适用场景​​:离线查询场景
  3. ​尾递归改写​

     
    // 将递归二分改为迭代(示例)
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (check(mid)) {
            ans = mid;
            high = mid - 1;  // 或 low = mid + 1
        } else {
            // 调整边界
        }
    }

    • ​优势​​:避免递归栈开销,提高性能

总结

通过精心设计的二分查找变体,我们高效解决了非严格递增序列上的四类区间查询问题。关键点在于:

  1. 通过 ​​下界查找​​ 和 ​​上界查找​​ 覆盖所有查询场景
  2. 严格处理 ​​边界条件​​ 和 ​​重复元素​
  3. 采用 ​​1-indexed设计​​ 简化坐标转换
  4. 通过 ​​防溢出技巧​​ 和 ​​输入输出优化​​ 提升稳定性

此方案在 O(n+qlogn) 时间复杂度内解决问题,完全满足题目约束条件,且能通过所有边界测试。

你可能感兴趣的:(蓝桥杯,算法,蓝桥杯,职场和发展,开发语言,数据结构)