资讯详情

资讯详情

JOISC 2019考试题解:三维成绩查询的高效处理

1. 题目背景与核心考察点P14340 [JOISC 2019] 考试/Examination是日本信息学奥林匹克竞赛中的一道经典题目。这道题主要考察选手对数据结构与算法的综合运用能力特别是对区间操作和高效查询的处理技巧。题目设定在一个模拟的考试系统中需要处理大量学生的成绩查询和统计需求。1.1 问题场景建模题目描述了一个包含N名学生的考试系统每个学生有三门科目的成绩。系统需要处理两种操作修改指定学生的单科成绩查询满足特定条件的学生数量这类问题在实际应用中非常常见比如在线教育平台的成绩管理系统、竞赛排名系统等都需要高效处理大量动态更新的数据并快速响应复杂查询。1.2 核心算法挑战这道题的难点在于需要同时考虑三门科目的复合条件查询数据规模大N≤10^5要求算法时间复杂度必须控制在O(N log N)或更好查询条件可能涉及多个维度的比较运算2. 解题思路分析与算法选择2.1 暴力解法及其局限性最直观的解法是对于每次查询遍历所有学生记录并统计满足条件的人数。这种方法修改操作O(1)查询操作O(N)总体复杂度O(QN)Q为操作次数当N和Q都达到10^5时这种解法显然无法在时间限制内完成。2.2 基于分块思想的优化考虑将学生分成若干块如每块√N个学生对每块维护一些统计信息预处理时间O(N)修改操作O(1)查询操作O(√N)虽然比暴力法有所改进但对于最大数据规模仍然不够高效。2.3 三维偏序问题的转化通过分析查询条件我们可以将问题转化为三维偏序问题每个学生对应三维空间中的一个点A,B,C查询相当于统计特定立方体区域内的点数这类问题通常可以使用KD-Tree或CDQ分治等高级数据结构解决。3. 基于线段树的高效解法实现3.1 数据结构设计我们采用线段树离散化的组合方案对B和C两科成绩进行离散化处理建立以B为第一维、C为第二维的二维线段树每个线段树节点维护对应区间的学生集合3.2 关键操作实现3.2.1 预处理与离散化// 离散化处理 vectorint discretize(const vectorint scores) { vectorint sorted scores; sort(sorted.begin(), sorted.end()); sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end()); return sorted; } // 获取离散化后的值 int get_rank(const vectorint discretized, int value) { return lower_bound(discretized.begin(), discretized.end(), value) - discretized.begin(); }3.2.2 线段树节点结构struct SegmentNode { int l, r; // 节点覆盖的B值范围 vectorpairint, int points; // (C值, A值)的有序列表 SegmentNode *left, *right; SegmentNode(int l, int r) : l(l), r(r), left(nullptr), right(nullptr) {} };3.3 查询操作实现查询时需要满足三个条件A ≥ TaB ≥ TbC ≥ Tcint query(SegmentNode* node, int b_l, int b_r, int c_min, int a_min) { if (!node || node-r b_l || node-l b_r) return 0; if (b_l node-l node-r b_r) { // 在C值有序列表中进行二分查找 auto it lower_bound(node-points.begin(), node-points.end(), make_pair(c_min, a_min)); return distance(it, node-points.end()); } return query(node-left, b_l, b_r, c_min, a_min) query(node-right, b_l, b_r, c_min, a_min); }4. 算法优化与性能分析4.1 时间复杂度优化通过上述设计预处理O(N log² N)修改操作O(log² N)查询操作O(log² N)这种复杂度对于N10^5的数据规模完全可接受。4.2 空间复杂度分析二维线段树的空间复杂度为O(N log N)在内存限制范围内。4.3 常数优化技巧使用非递归式线段树实现对离散化后的值进行压缩存储预先分配内存减少动态分配开销5. 完整代码实现与关键注释#include bits/stdc.h using namespace std; const int MAXN 1e55; struct Student { int a, b, c; } students[MAXN]; vectorint b_values, c_values; struct SegmentNode { int l, r; vectorpairint, int points; // (c, a) SegmentNode *left, *right; SegmentNode(int l, int r) : l(l), r(r), left(nullptr), right(nullptr) {} }; SegmentNode* build(int l, int r, const vectorpairint, int data) { SegmentNode* node new SegmentNode(l, r); if (l r) { for (const auto p : data) { if (p.first l) { node-points.emplace_back(p.second, 0); } } sort(node-points.begin(), node-points.end()); // 预处理A值的前缀最大值 for (int i node-points.size()-2; i 0; --i) { node-points[i].second max(node-points[i].second, node-points[i1].second); } return node; } int mid (l r) / 2; node-left build(l, mid, data); node-right build(mid1, r, data); // 合并左右子节点的点集 auto lp node-left-points; auto rp node-right-points; node-points.resize(lp.size() rp.size()); merge(lp.begin(), lp.end(), rp.begin(), rp.end(), node-points.begin()); return node; } int query(SegmentNode* node, int b_l, int b_r, int c_min, int a_min) { if (!node || node-r b_l || node-l b_r) return 0; if (b_l node-l node-r b_r) { auto it lower_bound(node-points.begin(), node-points.end(), make_pair(c_min, a_min)); return distance(it, node-points.end()); } return query(node-left, b_l, b_r, c_min, a_min) query(node-right, b_l, b_r, c_min, a_min); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, Q; cin N Q; // 读取初始数据并离散化 for (int i 0; i N; i) { cin students[i].a students[i].b students[i].c; b_values.push_back(students[i].b); c_values.push_back(students[i].c); } // 离散化处理 sort(b_values.begin(), b_values.end()); b_values.erase(unique(b_values.begin(), b_values.end()), b_values.end()); sort(c_values.begin(), c_values.end()); c_values.erase(unique(c_values.begin(), c_values.end()), c_values.end()); // 准备线段树构建数据 vectorpairint, int build_data; for (int i 0; i N; i) { int b_rank lower_bound(b_values.begin(), b_values.end(), students[i].b) - b_values.begin(); int c_rank lower_bound(c_values.begin(), c_values.end(), students[i].c) - c_values.begin(); build_data.emplace_back(b_rank, c_rank); } // 构建线段树 SegmentNode* root build(0, b_values.size()-1, build_data); // 处理查询 while (Q--) { int type; cin type; if (type 1) { int x, a, b, c; cin x a b c; // 更新操作实际实现需要考虑动态更新此处简化 } else { int ta, tb, tc; cin ta tb tc; int b_rank lower_bound(b_values.begin(), b_values.end(), tb) - b_values.begin(); int c_rank lower_bound(c_values.begin(), c_values.end(), tc) - c_values.begin(); int count query(root, b_rank, b_values.size()-1, c_rank, ta); cout count \n; } } return 0; }6. 常见问题与调试技巧6.1 边界条件处理离散化时注意处理重复值查询时注意开闭区间选择空指针检查6.2 性能优化验证使用随机生成的大数据测试对比暴力解法的结果确保正确性使用性能分析工具定位热点6.3 典型错误案例离散化后忘记更新查询条件线段树合并时未保持有序性内存管理不当导致泄漏7. 算法扩展与变种思考7.1 支持动态更新的改进当前实现假设数据是静态的。要支持动态更新可以考虑使用树状数组套线段树采用分块二分的方法实现真正的动态二维线段树7.2 更高维度的扩展如果增加更多科目维度可以考虑KD-Tree结构多维树状数组基于bitset的压缩表示7.3 离线处理方案如果所有查询可以提前获取采用CDQ分治可以将复杂度降为O(N log N)。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →