离散化+去重+二分查找

离散化

离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。 
通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如: 
原数据:1,999,100000,15;处理后:1,3,4,2; 
原数据:{100,200},{20,50000},{1,400}; 
处理后:{3,4},{2,6},{1,5};

离散化是程序设计中一个常用的技巧,它可以有效的降低时间复杂度。其基本思想就是在众多可能的情况中,只考虑需要用的值。离散化可以改进一个低效的算法,甚至实现根本不可能实现的算法。要掌握这个思想,必须从大量的题目中理解此方法的特点。例如,在建造线段树空间不够的情况下,可以考虑离散化。

数据的离散化

有些数据本身很大, 自身无法作为数组的下标保存对应的属性。如果这时只是需要这堆数据的相对属性, 那么可以对其进行离散化处理。当数据只与它们之间的相对大小有关,而与具体是多少无关时,可以进行离散化。 
例 1: 
91054 与 52143的逆序对个数相同。 
例 2: 
设有4个数: 1234567、123456789、12345678、123456 
排序:123456<1234567<12345678<123456789 
=>1<2<3<4 
那么这4个数可以表示成:2、4、3、1 
例 3: 
比如给你n个数:98998988,32434234,433234556,32434234,8384733,…… 
让你统计其中每个数出现的次数,传统的做法有好几种,比如一遍一遍的扫过去,比对叠加,这样算法的效率是O(n2),效率低下; 
再比如先排序,再统计连续的相同的个数,这里的效率已经有所提高了,不过假如上面的数据是一道线段树的题目给出的数据,那么建树需要的空间开销实在是太大了。 
再改进一下,采用哈希的方法,开一个大于其中最大数的数组并初始化为零,O(n)扫一下,在该数字对应的下标的元素上+1,如果对于比较小的数字还好说,但是对于上面出现的数字直接采用哈希对空间的开销是十分大的也是没有必要的,所以这里用到了数据的离散化。 
首先将数字排序:32434234,32434234,43324556,8384733,98998988 
去重后给予其对应的索引: 0,0,1,2,3 (一一映射) 
分别对应每个数,就可以简化很多操作,减少了很多不必要的资源开销。 
除了对于较大整数需要使用离散化之外,对于一些需要使用整型数据结构,但给出的数据却是小数的也可以使用离散化,将其索引为整数就可以了。

那么可以总结出离散化的步骤:

1、排序

2、去重

3、索引 
为了简化代码,我们采用STL离散化:

1 /*
2 用离散化之前先用 sort()排序,再用 unique() 进行去重
3 用 lower_bound() 或者 upper_bound() 进行二分查找位置
4 */
5 int a[n], b[n], sub[n];/// a[n]是即将被离散化的数组,b[n]是a[n]的副本,sub用于排序去重后提供离散化后的值
6 sort(sub, sub + n);
7 int size = unique(sub, sub + n) - sub;
8 for(int i = 0; i < n; i++)
9     a[i] = lower_bound(sub, sub + size, a[i]) - sub;///即a[i]为b[i]离散化后对应的值

uinque()函数

头文件

#include<iostream>

unique 的作用是“去掉”容器中相邻元素的重复元素,这里所说的“去掉”并不是真正把重复元素删除,它实质上是一个伪去除,是把重复的元素移到后面去了,然后依然保存到了原数组中,然后返回去重后最后一个元素的地址。
因为unique去除的是相邻元素的重复元素,所以使用之前需要排序。

sz=unique(b+1,b+n+1)-(b+1);//减去的(b+1) 起始地址
sz=unique(a,a+n)-a;

unique(a,a+n) - a返回的是去重后的数组长度

 

 

二分查找lower_bound()、upper_bonud()

1、lower_bound函数

       在一个非递减序列前闭后开区间[first,last)中,进行二分查找查找某一元素val,函数lower_bound()返回大于或等于val的第一个元素位置(即满足条件a[i]>=val(first<=i<last)的最小的i的值),当区间的所有元素都小于val时,函数返回的i值为last(注意:此时i的值是越界的!!!!!)。

STL中函数lower_bound()的代码实现(first是最终要返回的位置)

 

 1 int lower_bound(int *array, int size, int key)
 2 {
 3     int first = 0, middle, half, len;
 4     len = size;
 5     while(len > 0)
 6     {
 7         half = len >> 1;
 8         middle = first + half;
 9         if(array[middle] < key)
10         {
11             first = middle +1;
12             len = len - half -1;//在右边子序列中查找
13         }
14         else
15             len = half;//在左边子序列(包含middle)中查找
16     }
17 }

 

 

2、upper_bound函数

       在一个有序序列(升序或者降序)的区间中,进行二分查找某一元素val,函数upper_bound返回一个迭代器指向该区间中最后一个这个元素的下一个位置(简单的说就是返回能够将元素val插入区间的最后一个位置)。

 

STL中函数upper_bound()的代码实现(first是最终要返回的位置)

 

 1 int upper_bound(int *array, int size, int key)
 2 {
 3     int first = 0, len = size-1, half, middle;
 4  
 5     while(len > 0)
 6     {
 7         half = len >> 1;
 8         middle = first + half;
 9         if(array[middle] > key)//中位数大于key,在包含last的左半边序列中查找。
10             len = half;
11         else
12         {
13             first = middle + 1;//中位数小于等于key,在右半边序列中查找。
14             len = len - half - 1;
15         }
16     }
17     return first;
18 }

 

 

 

 

 

 

转载于:https://www.cnblogs.com/wkfvawl/p/9343696.html

<think>好的,我现在需要帮助用户了解AcWing平台上的离散化概念和实现方法。首先,我得回忆一下离散化的基本概念。离散化主要用于处理那些数值范围很大但实际数据点稀疏的情况,通过将大范围的数值映射到紧凑的连续整数,从而优化算法的时间和空间效率。例如,处理坐标范围很大的数据但实际只有少数几个点的情况。 接下来,用户提到了AcWing平台,可能需要结合平台上的课程内容来回答。根据提供的引用,特别是引用[2]、[3]、[4],可以看出AcWing的离散化方法主要涉及排序、二分查找。我需要将这些步骤详细说明,并给出代码示例,比如使用C++的vector来实现。 然后,用户可能想知道离散化的应用场景,比如区间和的统计问题,或者需要压缩数据空间的情况。比如在计算前缀和时,原始坐标可能非常分散,离散化后可以转换为连续的索引,减少计算量。 另外,需要注意实现中的细节,比如的必要性,以及如何通过二分查找快速找到原数据对应的离散化后的值。这里可能会引用到引用[3]中的内容,即使用二分法找到第一个大于等于x的位置作为映射后的值。 最后,生成相关问题的时候,要围绕离散化的实现步骤、应用场景、性能优势以及其他算法的结合,比如位运算等。这样可以帮助用户进一步巩固知识,并了解如何在实际问题中应用离散化。</think>### AcWing离散化算法详解 #### 一、离散化核心思想 离散化是一种将大范围稀疏数据映射到紧凑连续区间的算法,核心思想可概括为: $$原数据X \rightarrow 映射后的索引index$$ 例如处理坐标范围$[-10^9, 10^9]$但实际只使用$10^5$个点的情况[^2]。其核心步骤为: 1. 排序所有待离散化数值 2. 消除复元素 3. 使用二分查找建立映射关系 #### 二、AcWing实现模板(C++) ```cpp vector<int> alls; // 存储所有待离散化的值 sort(alls.begin(), alls.end()); // 排序 alls.erase(unique(alls.begin(), alls.end()), alls.end()); // // 二分查找映射函数 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; // 映射到1~n的连续区间 } ``` 此实现利用了位运算优化二分过程(`mid = l + r >> 1`等价于`(l + r)/2`)[^4] #### 三、典型应用场景 1. **区间和统计**:如AcWing 802题,处理坐标范围极大但操作次数有限的情况 2. **坐标压缩**:将稀疏坐标映射到连续索引,节省存储空间 3. **数据归一化**:消除数据绝对大小对算法的影响,保留相对顺序 #### 四、算法优势对比 | 处理方式 | 时间复杂度 | 空间复杂度 | 适用场景 | |----------|------------|------------|----------| | 原始数据 | O(1) | O(N) | 小范围 | | 离散化 | O(logN) | O(M) M<<N | 大范围稀疏数据 | #### 五、注意事项 1. 必须严格完成**排序--二分**三步骤 2. 二分查找建议使用左闭右闭区间写法 3. 离散化后的索引通常从1开始,避免边界问题 4. 需要同时处理原始数据和离散化后的映射关系[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值