【1】P3156 【深基15.例1】询问学号

P3156 【深基15.例1】询问学号

题目描述

\(n(n \le 2 \times 10^6)\) 名同学陆陆续续进入教室。我们知道每名同学的学号(在 \(1\)\(10^9\) 之间),按进教室的顺序给出。上课了,老师想知道第 \(i\) 个进入教室的同学的学号是什么(最先进入教室的同学 \(i=1\)),询问次数不超过 \(10^5\) 次。

输入格式

第一行 \(2\) 个整数 \(n\)\(m\),表示学生个数和询问次数。

第二行 \(n\) 个整数,表示按顺序进入教室的学号。

第三行 \(m\) 个整数,表示询问第几个进入教室的同学。

输出格式

输出 \(m\) 个整数表示答案,用换行隔开。

输入输出样例 #1

输入 #1

10 3
1 9 2 60 8 17 11 4 5 14
1 5 9

输出 #1

1
8
5

题解

(1)代码可优化点

  • 数据类型 (int vs long long):这是潜在的隐患。题目中学号上限为 \(10^9\)int(上限约 \(2.1 \times 10^9\))可以存储学号本身。但vector<int> list的索引是通过size_t(一种无符号整数类型)计算的,在处理超大数组时,为了避免索引计算可能出现的潜在溢出风险,推荐使用更能保证安全的long longsize_t类型来处理索引和大小。

  • 输入输出(I/O)优化:这是最重要的优化。面对\(2\times10^6\)的数据输入量,cin/cout的默认同步机制会拖慢速度。在你的代码开头加上这两行,代码效率会显著提升:

    ios::sync_with_stdio(false);
    cin.tie(0);
    
  • 缓存命中率与容器选择(静态数组 vs. vector:这是一个很关键的思维拓展点。你使用的 vector 非常安全方便,但它的数据存储在内存中。而静态数组(如 int a[2000005];,通常作为全局变量)存储在数据段(Data Segment)。

    在顺序访问的场景下,静态数组的内存访问模式更简单、可预测,对CPU缓存(Cache)更友好。而vector的堆内存可能因为碎片化或访问堆上元数据,导致缓存未命中(Cache Miss)的次数理论上更高。在数据规模极大且访问密集时,静态数组在顺序读取速度上有微小的理论优势

详细对比一下:

维度 vector<int> 静态数组 int arr[MAXN]
内存位置 堆 (Heap) 全局/静态存储区 (Data Segment)
空间分配 动态分配,需 push_back 编译时确定,固定大小
缓存命中率 对CPU缓存可能不够友好 对CPU缓存非常友好
安全性 提供 at() 越界检查,operator[] 无检查 无检查,越界会导致未定义行为
推荐场景 大小不确定、需要动态增长的场景 大小固定或可预估上限的场景

对于本题,数据规模确定(\(n \le 2\times10^6\)),使用vector完全正确。这里主要是从原理上探讨一下性能差异。

  • 内存优化:你可以合并操作,不需要把第二行、第三行的所有数据都存下来。可以边输入询问边输出结果,但这样做会失去I/O优化的部分优势。

  • 扩展性考量:如果题目未来询问次数极大且数据不改,使用更底层的数据结构(如普通数组)和内存管理可能会成为拉开差距的关键。

(2)代码实现

解法1:使用vector (你的思路,现代C++风格)

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 加速IO
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int n, m;
    cin >> n >> m;
    
    // 使用 vector 存储学号
    vector<long long> a(n + 1); // 多开1个空间,使学号从 a[1] 开始存储
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    
    // 直接输出询问的学号
    while (m--) {
        int idx;
        cin >> idx;
        cout << a[idx] << '\n'; // 使用 '\n' 代替 endl 更快
    }
    return 0;
}

这是最推荐、最均衡的AC解法,兼具安全与效率。

解法2:使用静态数组 (效率最高)

#include <iostream>
using namespace std;

const int MAXN = 2000005;
long long a[MAXN]; // 全局数组,索引从1开始使用[reference:5]

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    
    while (m--) {
        int idx;
        cin >> idx;
        cout << a[idx] << '\n';
    }
    return 0;
}

追求极致速度和稳定性的经典C风格解法,代码极其简洁。

关键总结

重点项 说明
数据范围 \(n \le 2\times 10^6\)\(m \le 10^5\),必须重视 I/O 效率
核心优化 使用 ios 解绑和 \n 是解题关键
数据结构 解法1 (vector) 安全方便;解法2 (静态数组) 极致高效
易错点 数组大小:记得开 n+1n+5 的余量,防止越界
posted @ 2026-05-11 20:44  炸酱面配小米酒  阅读(18)  评论(0)    收藏  举报