离散化算法的本质就是,将分布范围很大,但是密度很小的数据,变成分布范围很小,密度 很密的过程。例如,一个数组的元素是1,50,5000,60000,这个数组的范围跨度很大,但是密度很小 ,所以为了能够进行方便的查询和区间运算,我们将他们映射到连续的范围很小的数组中去。
例如:将1映射为新数组1的下标的位置,50映射到 新数组下标2的位置,以此类推。
而需要进行区间数据操作的时候,就需要将给出的区间下标转换为离散化后的下标进行数据操作就可以了。
离散化题目的思路想法
假定有一个无限长的数轴,数轴上每个坐标上的数都是 00。
现在,我们首先进行 n次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m次询问,每个询问包含两个整数 l和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n 和 m。
接下来 n行,每行包含两个整数 x和 c。
再接下来 m行,每行包含两个整数 l和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
思路:在题目中提到的数据的范围跨度很大,同时如果需要求得区间和的话,可以使用前缀和的方式。为了使用前缀和的方式需要降低区间的范围,以降低时间运行的复杂度。
具体实现方式:扫描需要用到的所有数据,开辟一个 n*数据长度+2*m个查询的数组,后面再进行去重排序和离散化,最后在进行前缀和输出就可以了!
注意:在这个算法中会创造两个数组,一个数组用来存储操作数据(包含数学运算和区间查询的所有下标 )alls[ ],一个直接用来记录操作之后的结果数组 res[ ]。
ok,后面解决一些可能有疑惑的地方:
1. 为什么需要开辟n+2m个长度的数组?
因为在解题的时候会创建一个数组专门用来存储需要操作的下标,下标与数就是对应关系,即存储的时候数据的大小和下标是相等的。所以,只需要将下标得到,并且存储在这个数组中,就可以对其进行操作了。
2. 为什么需要去重和排序?
去重的原因: 在前面的插入和数据操作的下标,跟后面进行数据区间和的查询的下标是有可能重合的,为了防止在通过遍历这个存储下标数组的时候,将结果数组加上了两次,出现数据不对的情况。
排序的作用:主要是为了在后续进行区间和的查询的时候能够得到正确的数值,因为如果不排序的话就会使得各个区间和并不对,是混乱的
例如: 在1的位置加2,3的位置加3,2的位置加上2,查询[1 , 3]的区间和,数组原始长度是5。
那么,通过处理后的 alls 数组的情况就是:[1,2,3,5] , 数组res 的结果就是:[2,2,3,0](假设下标重1开始)
如果只是去重的话,有可能出现不按照顺序出现的情况,当然上面的情况是一种很理想的情况,如果出现乱序的情况,比如:alls是[1,3,5,2]的话,res就变成了[2, 3 ,2, 0]就会导致后续在求前缀和的时候出现区间和不对的情况。
举一个例子:正确的区间和应该是:[1,3] -> [7],
没有排序出现顺序错误的情况下的结果是: [1,3] -> [5]
出现这种情况的原因,是因为后续直接进行前缀和的时候,直接将res 中的前面的数据加上的,并不会再次判断下标的问题。
3. 开辟的两个数组的作用的是什么?
第一个数组alls的作用: 用来记录需要进行操作的数据的下标和需要进行数学运算的数据,以及需要进行查询的区间数据。
第二个数组:是用来记录离散化之后,并且被操作之后的数据数组。
4. 如何求区间和和返回正确的数据?
这个就需要用到 res 数组了,直接对res数组求前缀和 s 数组,并且保存起来就可以了。
如何返回正确的数据: 将需要查询的区间值进行相同的离散化,找到离散化之后的下标,然后再在 s 数组中,使用s[ r ] - s[ l - 1 ]就可以了。
到这一步就可以更加深刻的理解为什么事先需要存入查询的区间值了,这一步是相当的女少口阿,因为在这一步的时候直接将[ 0, l ] 和[ 0 , r ] (l 和 r 就是需要查询的区间的左右端点的前缀和求出来了,然后直接使用s[ r ] - s[ l - 1 ]就可以求出需要求出的区间的数值了。
离散化数据的模板
//这一步是进行数据的去重和排序
//直接背一下这个方法就可以了
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
//这一步就是找到离散化之后的下标的位置
//这里展示的是二分查找的方法,当然可以使用hashMap直接得到原坐标和离散化之后的映射,这样就可以使得时间复杂度从log2(n) 变成O(1)
//这里使用的数组就是上面调用方法之后的数组alls
int find(int x){
int l = 0,r = all.size() - 1;
//这一步是为了找到第一个大于给定数据的位置
while(l < r){
int mid = l + r >> 1;
if(all[mid] >= x) r = mid;
else l = mid + 1;
}
//这一步加1,是为了后续在进行前缀和的时候可以直接从1开始
return r + 1;
}