别再硬算区间和了!用C++离散化搞定AcWing 802,效率提升百倍
2026/6/12 4:00:58 网站建设 项目流程

离散化算法实战:用C++高效解决AcWing 802区间和问题

在算法竞赛和编程面试中,我们经常会遇到处理大规模数据的问题。当数据范围极大但实际使用点稀疏时,传统的数组存储方式会带来巨大的内存浪费。这时,离散化技术就成为了解决问题的利器。本文将深入探讨如何利用C++的离散化技巧来高效解决AcWing 802区间和问题,让你的算法效率提升百倍。

1. 离散化技术核心原理

离散化本质上是一种数据压缩技术,它将原本分散在广阔数值空间的数据点映射到一个紧凑的连续区间内。想象一下,你需要在一条无限长的数轴上标记几个稀疏的点并进行操作——直接存储整条数轴显然不现实,而离散化则能将这些点"压缩"到一个可管理的大小。

离散化适用的典型场景包括:

  • 数值范围极大(如-10^9到10^9)
  • 实际使用的数值点相对较少(如10^5个)
  • 需要频繁进行区间查询或修改操作

离散化的三大核心步骤

  1. 收集所有需要处理的点:包括修改点和查询点
  2. 排序并去重:建立一一映射关系的基础
  3. 建立映射关系:将原始数值映射到紧凑的索引

离散化的关键在于保持原始数据的相对顺序不变,只是改变了它们的表示方式。这就像把散落在广阔草原上的几棵树重新编号,种到一个小花园里,但保持它们原来的相对位置关系。

2. AcWing 802问题解析

AcWing 802题目描述如下:给定一个无限长的数轴,初始所有位置值都为0。然后进行n次操作,每次在位置x上加c。接着进行m次查询,每次询问区间[l,r]内所有数的和。数据范围要求:-10^9≤x≤10^9,1≤n,m≤10^5。

2.1 传统方法的局限性

如果不使用离散化,最直观的做法可能是:

const int MAX = 2e9 + 10; int arr[MAX]; // 尝试创建一个覆盖-10^9到10^9的数组

这种方法存在明显问题:

  • 内存爆炸:需要约16GB内存(假设int为4字节)
  • 效率低下:即使内存足够,遍历大范围区间求和也非常耗时

2.2 离散化解决方案的优势

通过离散化,我们可以:

  • 将实际使用的n个修改点和2m个查询端点(每个查询有l和r)映射到约3×10^5的范围内
  • 内存使用从O(10^9)降到O(10^5)
  • 查询效率从O(区间长度)提升到O(1)(使用前缀和)

3. C++离散化实现详解

3.1 离散化准备工作

首先,我们需要收集所有需要处理的点:

vector<int> alls; // 存储所有需要离散化的值 vector<pair<int, int>> add; // 存储修改操作 vector<pair<int, int>> query; // 存储查询操作 // 读取修改操作 for (int i = 0; i < n; i++) { int x, c; cin >> x >> c; add.push_back({x, c}); alls.push_back(x); } // 读取查询操作 for (int i = 0; i < m; i++) { int l, r; cin >> l >> r; query.push_back({l, r}); alls.push_back(l); alls.push_back(r); }

3.2 离散化核心步骤

离散化的核心在于三步操作:

  1. 排序:将所有点按从小到大排序
  2. 去重:去除重复的点
  3. 建立映射:创建从原始值到紧凑索引的映射
// 排序 sort(alls.begin(), alls.end()); // 去重 alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 二分查找实现映射函数 int find(int x) { int l = 0, r = alls.size() - 1; while (l < r) { int mid = l + r >> 1; if (alls[mid] >= x) r = mid; else l = mid + 1; } return r + 1; // 映射到1-based索引 }

在实际应用中,映射到1-based还是0-based索引取决于个人习惯。1-based索引在处理前缀和时通常更方便,因为可以避免对i-1的边界检查。

3.3 自定义unique函数实现

虽然STL提供了unique函数,但了解其实现原理很有必要:

vector<int>::iterator my_unique(vector<int>& a) { int j = 0; for (int i = 0; i < a.size(); i++) { if (!i || a[i] != a[i-1]) { a[j++] = a[i]; } } return a.begin() + j; }

这个实现使用了双指针技巧:

  • 指针i遍历整个数组
  • 指针j指向下一个唯一元素应该存放的位置
  • 当遇到与前一个元素不同的值时,将其存入j位置并递增j

4. 完整解决方案与性能分析

4.1 完整AC代码

#include <iostream> #include <vector> #include <algorithm> using namespace std; typedef pair<int, int> PII; const int N = 3e5 + 10; // n + 2m int a[N], s[N]; // 离散化后的数组和前缀和数组 vector<int> alls; vector<PII> add, query; int find(int x) { int l = 0, r = alls.size() - 1; while (l < r) { int mid = l + r >> 1; if (alls[mid] >= x) r = mid; else l = mid + 1; } return r + 1; } int main() { int n, m; cin >> n >> m; // 读取添加操作 for (int i = 0; i < n; i++) { int x, c; cin >> x >> c; add.push_back({x, c}); alls.push_back(x); } // 读取查询操作 for (int i = 0; i < m; i++) { int l, r; cin >> l >> r; query.push_back({l, r}); alls.push_back(l); alls.push_back(r); } // 离散化处理 sort(alls.begin(), alls.end()); alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 处理添加操作 for (auto item : add) { int x = find(item.first); a[x] += item.second; } // 构建前缀和数组 for (int i = 1; i <= alls.size(); i++) { s[i] = s[i - 1] + a[i]; } // 处理查询 for (auto item : query) { int l = find(item.first), r = find(item.second); cout << s[r] - s[l - 1] << endl; } return 0; }

4.2 复杂度分析

让我们对比离散化前后的性能差异:

指标离散化前离散化后
空间复杂度O(2×10^9)O(3×10^5)
添加操作O(1)O(log n)
查询操作O(r-l)O(log n)
预处理O(n log n)
适用数据规模极小1≤n,m≤10^5

虽然离散化增加了一定的预处理开销(排序和去重),但它将问题的规模从不可行变为可行。对于n,m=10^5的情况:

  • 预处理:O(n log n) ≈ 1.6×10^6次操作
  • 每次查询:O(log n) ≈ 20次操作
  • 总操作量:完全可以接受

4.3 常见错误与调试技巧

在实现离散化时,容易犯的错误包括:

  1. 忘记去重:导致相同的值映射到不同位置
  2. 二分查找实现错误:可能陷入死循环或返回错误索引
  3. 索引处理不当:1-based和0-based混淆导致边界错误
  4. 内存分配不足:没有考虑到所有需要离散化的点

调试时可以:

  • 打印alls数组确认排序和去重正确
  • 验证find函数的返回值是否符合预期
  • 检查前缀和数组的构建是否正确

5. 离散化技术的扩展应用

离散化不仅适用于区间和问题,还可以广泛应用于:

  1. 坐标压缩:在图形学或几何问题中处理大范围坐标
  2. 稀疏矩阵存储:只存储非零元素的位置和值
  3. 时间点处理:当时间范围很大但事件点很少时
  4. 统计问题:如求逆序对、区间众数等

例如,考虑这样一个问题:给定平面上的n个点(坐标范围很大但点稀疏),求每个点周围单位正方形内的点数。离散化可以将坐标映射到紧凑范围,然后使用二维前缀和来高效解决。

6. 性能优化进阶技巧

为了进一步提升离散化解决方案的性能,可以考虑:

  1. 手写二分查找:避免STL lower_bound的开销
  2. 哈希映射替代:当不保持顺序很重要时,可以用unordered_map
  3. 离线处理:如果问题允许,先收集所有操作再统一处理
  4. 并行排序:对于极大数据量,考虑并行化预处理阶段
// 优化版的find函数 inline int find(int x) { return lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1; }

这个版本使用了STL的lower_bound,代码更简洁,但可能比手写二分稍慢。在实际比赛中,根据数据特点选择合适的实现方式。

7. 实际应用中的经验分享

在多次编程竞赛中应用离散化技术后,我总结出以下几点经验:

  1. 预估数组大小:通常开3×10^5足够(n + 2m)
  2. 统一处理所有点:包括修改点和查询点
  3. 保持代码模块化:将离散化部分封装成独立函数
  4. 注意边界条件:特别是当查询点超出所有修改点范围时
  5. 测试极端情况:如所有点相同、单点多次修改等

离散化是处理稀疏大数据问题的利器,掌握它能让你的算法能力提升一个档次。在AcWing 802这样的问题中,它不仅是解决方案,更是展示算法思维优势的典范——通过巧妙的映射,将不可能变为可能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询