一. 模板
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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, 2, ...n
}
二. 总结
-
代码思路
-
为什么要离散化呢?
因为存储的下标实在太大了,如果直接开这么大的数组,根本不现实,第二个原因,本文是数轴,要是采用下标的话,可能存在负值,所以也不能,所以有人可能会提出用哈希表,哈希表可以吗?答案也是不可以的,因为哈希表不能像离散化那样缩小数组的空间,导致我们可能需要从-e9遍历到1e9(此处的含义就是假如我们需要计算1e-9和1e9区间内的值,那我们需要从前到后枚举,无论该值是否存在),因为哈希表不能排序,所以我们一般不能提前知道哪些数轴上的点存在哪些不存在,所以一般是从负的最小值到正的最大值都枚举一遍,时间复杂度太高,于是就有了本题的离散化。
- 离散化的本质,是映射,将间隔很大的点,映射到相邻的数组元素中。减少对空间的需求,也减少计算量。
三. 例题
* 802. 区间和
AC代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 3e5 + 10; // 最大存放x,l,r的最大值之和
vector<int> alls; // 用来保存真实的下标和想象的下标的映射关系
vector<PII> add, query; // 原来保存操作输入的值
int a[N], s[N];
int n, m;
// 在alls[]中二分查找,将值映射为下标
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 l + 1; // 这里+1是为了方便后续求前缀和
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; ++i) {
int x, c;
cin >> x >> c;
alls.push_back(x); // 存放下标进行映射
add.push_back({x, c}); // 存放添加的值
}
for (int i = 0; i < m; ++i) {
int l, r;
cin >> l >> r;
alls.push_back(l); // 存入查询左边界
alls.push_back(r); // 存入查询右边界
query.push_back({l, r}); // 存放查询的边界值
}
// 排序
sort(alls.begin(), alls.end());
// 去重
alls.erase(unique(alls.begin(), alls.end()), alls.end());
// 处理插入,并将插入的值存入a[]中对应的映射位置
for (auto item : add) {
int x = find(item.first);
a[x] += item.second;
}
// 求出前缀和并存放进s[]中
for (int i = 1; i <= alls.size(); ++i) s[i] = s[i - 1] + a[i];
// 处理查询
for (auto item : query) {
int l = find(item.first);
int r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}