离散化

应用场景

离散化的主要目的是将原本连续或稀疏的数值范围映射到一个较小的整数范围,以便更高效地进行处理。以下是一些常见的离散化应用场景:

  1. 数组下标映射:将很大的下标或不连续的下标映射到一个连续的小范围,便于使用数组或其他数据结构。
  2. 数值范围压缩:将连续的数值(如浮点数)映射到有限的整数范围,以便进行统计分析或构建直方图。
  3. 数据预处理:在机器学习中,将连续特征离散化为分类特征,可以简化模型的复杂性,或者用于某些需要离散输入的算法。
  4. 空间压缩:在计算几何中,将坐标离散化以减少计算复杂度。

离散化不仅仅是针对数组下标,它可以应用于任何需要将数据映射到更小的范围以便于处理的场景。

步骤

  1. 收集数据
  2. 去重排序
  3. 建立映射关系
  4. 应用映射

板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
}

这是yxc大佬的板子,不过我本人的二分查找不是这么写的,所以我下面例题的解答代码使用的二分也不是这个。

例题-区间和

https://www.acwing.com/problem/content/804/

题目描述

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。

现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。

接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。

输入格式

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含两个整数 x 和 c。

再接下来 m 行,每行包含两个整数 l 和 r。

输出格式

共 m 行,每行输出一个询问中所求的区间内数字和。

数据范围

x很大,10^9

nm只到10^5

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PII;

LL n, m;
LL a[300010], s[300010];
vector<LL> alls;
vector<PII> adds, querys;

LL find(LL x) { // 二分查找:下标映射
LL l = -1, r = alls.size();
while (l + 1 < r) {
LL mid = l + (r - l) / 2;
if (alls[mid] < x)
l = mid;
else
r = mid;
}
return r;
}

int main() {
// 输入
cin >> n >> m;
for (LL i = 0; i < n; i++) {
LL x, c;
cin >> x >> c;
alls.push_back(x);
adds.push_back({x, c});
}
for (LL i = 0; i < m; i++) {
LL l, r;
cin >> l >> r;
alls.push_back(l);
alls.push_back(r);
querys.push_back({l, r});
}

// 排序去重
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());

// 插入操作
for (auto item: adds) {
LL x = item.first, c = item.second;
a[find(x)] += c;
}

// 计算前缀和
s[0] = a[0];
for (LL i = 1; i < 300010; i++) {
s[i] = s[i - 1] + a[i];
}

// 查询操作
for (auto item: querys) {
LL l = item.first, r = item.second;
cout << s[find(r)] - s[find(l) - 1] << endl;
}

return 0;
}