离散化:
对于一些应用实例,有时只会用到数据的相对大小,而不在意数据本身的大小
例:在区间涂色问题中,依次给区间涂色,后涂色的区间会覆盖前区间。现在求剩下几种颜色
可以发现,这个问题中,有用的就只是区间的相对位置关系,而不在于区间本身的大小。如:
[1, 3] 涂白色,[6, 7] 涂黑色
[1, 3] 涂白色,[10000006, 10000007] 涂黑色
最后都只有两种颜色。但是若用线段树维护,第一种情况要区间长度 7,第二种情况却需要长度 10000007,太浪费时间空间
这里离散化就派上用场了。离散化就是把很大的区间,映射到小的区间中
用到的STL函数:
- sort():排序。若原数据是无序的,就需要先排序之后再处理
- unique():把相邻重复元素放到末尾,用不重复元素补上,返回不重复元素末尾地址。若原数据有序,则不重复元素也有序
- lower-boud():返回第一个不小于指定值的地址
离散数组,左端点,右端点
int disc[MAXN], L[MAXN], R[MAXN];
读入数据
for (int k = 0; k < n; k++) {
scanf("%d%d", L + k, R + k);
disc[tot++] = L[k];
disc[tot++] = R[k];
}
若原数据无序,则需要排序后再去重
sort(disc, disc + tot);
去重,得到不重复元素的个数。此时数组中前面的不重复元素保持有序
int size = (int)(unique(disc, disc + tot) - disc);
进行映射,拿该元素在disc数组中的位置作为映射后的新下标
for (int i = 0; i < n; i ++) {
L[i] = (int)(lower_bound(disc, disc + size, L[i]) - disc + 1);
R[i] = (int)(lower_bound(disc, disc + size, R[i]) - disc + 1);
}
离散化bug:
这样离散的方式存在bug,我们举个例子
[1, 10]、[1,6]、[7,10] ,剩下 2 种颜色
[1, 10]、[1,6]、[9,10] , 剩下 3 种颜色
但是离散化之后,区间全都变成
[1, 4]、[1,2]、[3,4] ,剩下 2 种颜色,很明显有一些情况会出错
分析:
我们维护了一些区间之后,存储的不仅仅是哪些区域有颜色。从另一个角度看,同时也反映了哪些区间是空白的
如:[1,6]、[9,10] 两个区间,就告诉了我们 [7, 8] 区间是空白的
然而,离散化的处理,就是把靠近的两个数设成相邻,把它们之间的空间设为 0 ,这样会丢失空白的那部分数据
本身 6 与 9 之间有空白,离散化之后,变成 2 与 3。这与 6、7 的离散结果无异,空白信息被丢失,对结果造成影响
解决:
有两种解决方案
1、加点法:若两个相邻的数的差值大于 1 ,也就是说两数之间有空白,就在它们之间插入一个点,用来体现空白
实现代码:
int disc[MAXN], L[MAXN], R[MAXN];
for (int k = 0; k < n; k++) { //读入数据
scanf("%d%d", L + k, R + k);
disc[tot++] = L[k];
disc[tot++] = R[k];
}
sort(disc, disc + tot); //若原数据无序,则需要排序后再去重
int size = (int)(unique(disc, disc + tot) - disc); //去重,得到不重复元素的个数。此时数组中前面的不重复元素保持有序
int temp = size; //加点,修复离散bug
for (int i = 1; i < temp; i++)
if (disc[i] - disc[i - 1] > 1)
disc[size++] = disc[i - 1] + 1;
sort(disc, disc + size);
for (int i = 0; i < n; i ++) { //进行映射,拿该元素在disc数组中的位置作为映射后的新下标
L[i] = (int)(lower_bound(disc, disc + size, L[i]) - disc + 1);
R[i] = (int)(lower_bound(disc, disc + size, R[i]) - disc + 1);
}
2、在维护线段树时,改变左右子树的划分方式
原来是: [l, mid]、[mid + 1, r] 改为:[l, mid]、[mid, r] 便可以解决