离散化思维的应用
- 离散化思维
-
- 离散化思路:排序+去重+二分查找
- 离散化思路:排序+哈希
- [P1496 火烧赤壁 - 洛谷](#P1496 火烧赤壁 - 洛谷)
- [P3740 贴海报 - 洛谷 区间覆盖情况](#P3740 贴海报 - 洛谷 区间覆盖情况)
- OJ参考
毕业前最后的燃烧。
离散化思维
当题目中数据的范围很大 ,但是数据的总量不是很大。此时如果需要用数据的值来映射数组的下标时,就可以用离散化的思想先预处理一下所有的数据,使得每一个数据都映射成一个较小的值。之后再离散化之后的数去处理问题。
比如: [ 99 , 9 , 404 , 1145141919810 ] [99, 9, 404, 1145141919810] [99,9,404,1145141919810] 离散之后就变成 [ 2 , 1 , 3 , 4 ] [2, 1, 3, 4] [2,1,3,4]。
有的题若不使用离散化处理的话,数据量是 10 9 10^9 109 将开不了这么大的数组,此时将束手无策。
案例:给定 n n n 个数,离散化之后,输出每一个数离散化之后的值。
测试数据:
10
1999999
12
1999999
-48444
568
12
-100
-2845630
100000001
263
离散化思路:排序+去重+二分查找
首先对这些数据进行排序,然后去重,最后通过二分查找数的索引,此时它们的下标索引组成的集合即为它们离散化后的值。
参考程序:
cpp
#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> a, // 原始数据
disc; // 离散化结果
// 二分寻找左边界,查找x的位置
int find(int x) {
int l = 1, r = disc.size();
while (l < r) {
int mid = (l + r) / 2;
if (disc[mid] >= x)
r = mid;
else
l = mid + 1;
}
return l;
}
int main() {
cin >> n;
a.resize(n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> a[i];
disc.push_back(a[i]);
}
// 离散化
sort(disc.begin() + 1, disc.end()); // 排序
unique(disc.begin() + 1, disc.end()); // 用STL工具去重
for (int i = 1; i <= n; i++)
printf("%10d", a[i]);
cout << "\n";
for (int i = 1; i <= n; i++)
printf("%10d", find(a[i]));
cout << "\n";
return 0;
}
将输出整理成表格,可看到每个数在这个集合中都有指定的索引。
|------|---------|----|---------|--------|-----|----|------|----------|-----------|-----|
| 离散化前 | 1999999 | 12 | 1999999 | -48444 | 568 | 12 | -100 | -2845630 | 100000001 | 263 |
| 离散化后 | 7 | 4 | 7 | 2 | 6 | 4 | 3 | 1 | 8 | 5 |
离散化思路:排序+哈希
哈希表和红黑树自带去重,红黑树还自带排序。但红黑树的查询效率大多数情况低于哈希,所以这个离散化思路的主角是哈希,但缺少的有序需要排序来弥补。
这里的哈希在 C++ 中可用 unordered_map 和 unordered_set 表示,红黑树在 C++ 中可用 map 和 set 表示。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
vector<int> a; // 原始数据
vector<int> disc; // 经过排序的原始数据
unordered_map<int, int> ump; // <原始数据, 离散化结果>
int main() {
cin >> n;
a.resize(n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> a[i];
disc.push_back(a[i]); // 数据准备阶段
}
// 离散化
sort(disc.begin(), disc.end()); // 排序
for (int i = 0, cnt = 0; i < disc.size(); i++) {
if (ump.count(disc[i])) // 去重
continue;
ump[disc[i]] = ++cnt; // 当前这个值是第几号元素
}
// 离散化结果展示或其他处理
for (int i = 1; i <= n; i++)
printf("%10d", a[i]);
cout << "\n";
for (int i = 1; i <= n; i++)
printf("%10d", ump[a[i]]);
cout << "\n";
return 0;
}
离散化是一种处理数据的技巧,模版不固定,根据算法思想就可以实现。并且实现离散化的方式也可以在原有模板的基础上修改。
P1496 火烧赤壁 - 洛谷
贪心算法:区间问题
区间问题。
首先是贪心策略:对每个区间的左端点进行排序。然后合并互相重叠的区间,直至无法合并时,再将区间长度进行统计。
cpp
#include <bits/stdc++.h>
using namespace std;
struct Area {
int x;
int y;
bool operator<(const Area &aim) const {
return x < aim.x;
}
};
vector<Area> num;
int main() {
int n, ans = 0;
cin >> n;
num.resize(n + 1, {0, 0});
for (int i = 1; i <= n; i++)
cin >> num[i].x >> num[i].y;
sort(num.begin() + 1, num.end()); // 区间按左端点排序
for (int id = 1; id <= n; ++id) { // 合并区间
int x = num[id].x, y = num[id].y;
while (id + 1 <= n && y > num[id + 1].x) {
y = max(y, num[id + 1].y); // 更新重叠区间的右端点
id++;
}
ans += abs(y - x);
}
cout << ans;
return 0;
}
离散化处理+差分
若没有数据量的限制,还可以用差分算法统一修改区间内的元素的值。但既然有,想用差分解决,就需要先离散化。
首先可将所有的区间并排放在一起,例如本题的测试样例,可创建数组 disc[]={-1,1,2,5,9,11} ,求出它们的离散化结果:ump[]={1,2,3,4,5,6} ,ump 是 unordered_map 的简写。
题意问的是着火区域的长度,对每个元素来说,着火不着火无非就是一种状态。所以可维护一个全 0 的数组,然后根据已有区间信息进行差分运算,然后还原。例如对测试样例维护一个差分数组,然后枚举区间进行差分操作,整个表格如下:
还原 f 1 0 1 2 1 0 差分后 f 1 − 1 1 1 − 1 − 1 差分操作 + 1 − 1 + 1 + 1 − 1 − 1 差分前 f 0 0 0 0 0 0 离散化结果 u m p 1 2 3 4 5 6 原始数据 d i s c − 1 1 2 5 9 11 \begin{array}{|c|c|c|c|c|c|c|}\hline还原f&1&0&1&2&1&0\\\hline差分后f&1&-1&1&1&-1&-1\\\hline差分操作&+1&-1&+1&+1&-1&-1\\\hline差分前f&0&0&0&0&0&0\\\hline离散化结果ump&1&2&3&4&5&6\\\hline原始数据disc&-1&1&2&5&9&11\\\hline\end{array} 还原f差分后f差分操作差分前f离散化结果ump原始数据disc11+101−10−1−102111+103221+10451−1−10590−1−10611
然后根据还原后的结果,凡是大于 0 的区域,都可根据离散化结果之前的原始数据进行长度计算。例如这里,第1段大于0的是 [1,2) ,第 2 段大于 0 的是 [3,6) , 2 段的长度总和是 d i s c [ 2 ] − d i s c [ 1 ] + d i s c [ 6 ] − d i s c [ 3 ] = 1 − ( − 1 ) + 11 − 2 = 11 disc[2]-disc[1]+disc[6]-disc[3]=1-(-1)+11-2=11 disc[2]−disc[1]+disc[6]−disc[3]=1−(−1)+11−2=11 , d i s c disc disc 表示根据离散化结果查询原始数据。
按照这个思路也能解决这个题。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
int n;
int a[N], b[N]; // 区间的原始数据
int pos;
int disc[N * 2]; // 离散化前的原始数据
unordered_map<int, int> ump;
int f[N * 2]; // 差分数组载体
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
disc[++pos] = a[i];
disc[++pos] = b[i];
}
// 离散化操作
sort(disc + 1, disc + 1 + pos); // 排序
pos = unique(disc + 1, disc + 1 + pos) - (disc + 1); // 去重
for (int i = 1; i <= pos; i++) // 插入哈希表建立映射关系
ump[disc[i]] = i;
// 差分操作
for (int i = 1; i <= n; i++) {
int l = ump[a[i]], r = ump[b[i]];
f[l]++;
f[r]--;
}
// 差分数组还原
for (int i = 1; i <= pos; i++)
f[i] += f[i - 1];
// 根据大于0的区域获得区间长度
int ans = 0;
for (int i = 1; i <= pos; i++) {
int j = i;
while (j <= pos && f[j] > 0)
j++;
ans += disc[j] - disc[i]; // 区间的长度是用原始数据相减
i = j; // 跳过处理过的区间
}
cout << ans << endl;
return 0;
}
P3740 贴海报 - 洛谷 区间覆盖情况
P3740 [HAOI2014\] 贴海报 - 洛谷](https://www.luogu.com.cn/problem/P3740)
读题:贴海报在一面墙上,新海报可以覆盖旧海报,问人能看到的海报数。
首先能想到的解法是模拟整个流程:根据海报的位置将指定区域内的数组用指定的值覆盖表示贴海报,贴完所有海报后统计不同的数字个数即为答案。时间复杂度 O ( n m ) \\text{O}(nm) O(nm) ,数据量来到 10 10 10\^{10} 1010 ,但时间限制只有 1 秒,所以不能用这个方式。
虽然墙的长度很长,但区间个数很少,此时就可以将将所有区间的数字收集起来进行离散化处理,然后根据区间对数组进行赋值覆盖处理,再统计不同的数的个数。但这个思路有严重的问题:
对这个样例:
6 3
2 5
2 3
5 6
离散化后的结果:
2 3 5 6
1 2 3 4
若是正常模拟,可以看到 3 张海报:

但离散化之后,区间实际上是缩小了的,所以 `[3,5]` 会被缩小,使得这个区域的海报不可见。这里的解决方式是在将 ( x , y ) (x,y) (x,y) 进行离散化处理时,顺便将 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1) 也进行离散化处理。
但这个思路也不完全适用所有情况,需要实际分析。
```cpp
#include