1. 概念
离散化是一种将无限空间中的有限个体,映射到有限空间中的方法,并且保持它们原有的相对顺序。
离散化只关心空间中数据的大小关系,并不关心数据的具体值。将一组分布稀疏、范围很大的原始数据,压缩成一组连续的、紧密的新数据,从而降低算法的空间和时间复杂度。
2. 思路
当给定一个无限大的元素空间中,选取有限个元素进行存放在一个新空间中。这个时候可以使用离散化进行存储,保持有限个元素之间的顺序不变,存储在新空间中。
或者当给定一个值域非常大的数组,而数组元素个数相对值域非常小,这个时候可以使用离散化,使其存储在有限的空间中。
例如:数组a {1, 3, 100, 2000, 500000},我们可以将其依次存储在[0, 1, 2, 3, 4]上。
3. 步骤
- 数组a中可能会存在重复元素,因此我们首先需要对a数组进行去重操作。
- 求出x离散化后的值。由于a是有序的,因此相当于求x的值在a中的下标是多少。
4. 扩展
4.1. unique(alls.begin(), alls.end()):
将alls数组中所有元素进行去重,将所有不重复的元素放到最前面,并且返回去重之后新数组的尾端点。
4.2. alls.erase(alls.begin(), alls.end()):
删除alls.begin()与alls.end()中间的所有元素。
4.3. alls.erase(unique(alls.begin(), alls.end()), alls.end()):
实现对alls去重并且删掉多余元素。
4.4. unique()实现:
原数组a:1 1 2 2 2 3 4 5 5 5 5 6
去重后的数组a:1 2 3 4 5 6 4 5 5 5 5 6
vector<int>::iterator 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;
}
5. 代码模板
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
}
6. 例题
6.1. 802. 区间和 - AcWing题库
这道题首先想到的就是前缀和+差分进行解题,但是这道题中元素下标非常大,最大可达到10^9,最小可以达到-10^9。
像是这种数据范围非常大,但是实际有意义的个数却很少,因此我们可以使用离散化进行解题。
这道题中,n和m的范围都是10^5,因此我们能够用到的下标最多只有n+2m个,也就是只会使用到3x10^5个坐标。共n个元素,m个l下标和r下标。
首先先求x离散化对应的下标k,对x+c也就相当于对a[k]+c;下标l和r也同理,将它们离散化成对应的kl和kr,然后求a[kl]到a[kr]的和即可。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 将所有操作读入
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
// a是存的数组,s是前缀和
int a[N], s[N];
// alls是存的所有需要离散化的值,以及所有需要操作的下标
vector<int> alls;
// 所有插入操作用add来存,求和操作区间用query存
vector<PII> add, query;
// 求x离散化后的结果
// 将所有数映射到从1开始的自然数
// 后续操作使用到了前缀和,因此使用从1开始的数组更加方便
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()
{
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++)
{
int x, c;
scanf("%d%d", &x, &c);
add.push_back({x, c});
alls.push_back(x);
}
for(int i = 0; i < m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
query.push_back({l, r});
alls.push_back(l);
alls.push_back(r);
}
// 对alls排序,去重
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);
int r = find(item.second);
cout << s[r] - s[l-1] << endl;
}
return 0;
}