离散化是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
即离散化是在不改变数据相对大小的条件下,对数据进行相对的缩小。
如:对于1,999,100000.15 处理后变为 1,3,4,2
对于数据更关注相对大小时可以应用离散化。
模版一
数组离散化
对于数组离散化分为四步:
- 创建原数组副本
- 对副本数组进行排序
- 对排序后的副本进行去重
- 对离散化后的值进行匹配
for(int i = 1; i <= n; i++)
temp[i] = arr[i]; //创建原数组副本
sort(temp + 1, temp + n + 1); //对副本元素进行排序
//unique函数对排序副本去重,并获取去重后的数组长度
int len = unique(temp + 1, temp + n + 1) - (temp +1);
for(int i = 1, i <= n; i++)
//查询原数组各元素在副本中的下标,下标即为排名,将其作为离散化后的值
arr[i] = lower_bound(temp + 1, temp + len + 1, arr[i]) - temp;
unique函数
unique函数主要用于去除相邻的重复元素。其不会真正删除元素,而是将不重复的元素保留在前面,并返回一个迭代器,该迭代器指向新的逻辑末尾。
注意unique只会去除相邻的重复元素,所以使用前要对元素进行排序操作。
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int arr[8] = {1, 2, 2, 3, 4, 4, 4, 5};
// 使用 unique 去除相邻的重复元素
int* new_end = unique(arr, arr + 8);
// 计算去重后的数组长度
int new_size = new_end - arr;
// 输出去重后的数组
for (int i = 0; i < new_size; i++) {
cout << arr[i] << endl;
}
return 0;
}
/*
输出结果:
1
2
3
4
5
*/
lower_bound函数
lower_bound
函数是 C++ 标准库中的一个算法,用于在已排序的范围内查找第一个不小于给定值的元素。它返回一个指向该元素的迭代器。如果没有不小于给定值的元素,则返回 end()
迭代器。
函数原型
- 默认比较方式:
template<class ForwardIt, class T>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);
-
first
,last
:指向要查找范围的迭代器。value
:要查找的值。
- 使用自定义比较函数:
template<class ForwardIt, class T, class Compare>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp);
-
comp
:自定义比较函数或谓词,用于定义元素的排序规则。
返回值
- 返回一个指向第一个不小于
value
的元素的迭代器。 - 如果
value
大于范围内的所有元素,则返回last
。
使用示例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {10, 20, 30, 40, 50};
// 查找第一个不小于25的元素
auto it = std::lower_bound(vec.begin(), vec.end(), 25);
if (it != vec.end()) {
std::cout << "First element not less than 25 is " << *it << std::endl;
} else {
std::cout << "No element found not less than 25" << std::endl;
}
return 0;
}
输出结果
First element not less than 25 is 30
代码解释
lower_bound(vec.begin(), vec.end(), 25)
在vec
中查找第一个不小于25
的元素。- 由于
30
是第一个不小于25
的元素,所以it
指向的是30
。
时间复杂度
lower_bound
的时间复杂度是 O(log n)
,因为它使用的是二分查找算法。这使得它非常高效,特别是在处理大型有序数据集时。
注意事项
lower_bound
需要作用于已排序的范围,否则结果是不确定的。- 在返回的迭代器位置插入
value
可以保持容器的有序性。
vector离散化
vector<int> arr, temp; //定义向量arr,以及其副本temp
sort(temp.begin(), temp.end()); //对副本进行排序
//对排好序的副本进行去重操作
temp.erase(unique(temp.begin(), temp.end()), temp.end());
//获取离散化之后各变量所对应的值
for(int i = 0; i < n; i++)
arr[i] = lower_bound(temp.begin(), temp.end(), arr[i]) - temp.begin();
erase函数
erase
函数在 C++ 标准库中的容器(如 vector
, deque
, list
等)中用于删除元素或删除指定范围内的元素。其主要作用是调整容器的大小,并且删除元素后容器中的其余元素会向前移动以填补空缺。
使用方式
- 删除单个元素:
//iterator表示迭代器
iterator erase(iterator pos);
示例:
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.erase(vec.begin() + 2); // 删除索引为2的元素,即3
// vec 现在是 {1, 2, 4, 5}
-
pos
:要删除的元素的位置。- 返回值:返回一个指向删除元素之后元素的迭代器。如果删除的是最后一个元素,则返回容器的
end()
迭代器。
- 删除指定范围的元素:
iterator erase(iterator first, iterator last);
示例:
std::vector<int> vec = {1, 2, 3, 4, 5};
// 删除索引范围[1, 4)内的元素,即2, 3, 4
vec.erase(vec.begin() + 1, vec.begin() + 4);
// vec 现在是 {1, 5}
-
first
:要删除的第一个元素的位置。last
:要删除的范围的结束迭代器(不包含该位置)。- 返回值:返回一个指向删除范围之后第一个元素的迭代器。如果删除的是最后一个元素或整个范围,则返回容器的
end()
迭代器。
注意事项
- 删除操作后迭代器的有效性:在
vector
或deque
中,删除元素会导致后续元素位置发生改变,这可能使得指向这些元素的迭代器失效。因此,在erase
操作后,通常需要更新迭代器。 - 时间复杂度:在
vector
和deque
中,erase
的时间复杂度是线性的 (O(n)
),因为需要移动删除元素后的所有元素。而在list
中,erase
的时间复杂度是常数 (O(1)
),因为列表是链式结构,不需要移动元素。
总结
erase
函数用于删除容器中的一个或多个元素,并调整容器的大小。它通常与 unique
函数结合使用,以删除容器中的重复元素。
二分法获取离散化后对应的值
vector<int> alls;
//利用二分法找到第一个大于等于x的位置
int find(int x){
int l = 0, r = alls.size() - 1; //alls表示排序去重后的副本
while(l < r){
int mid = l + r >> 1;
if(alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;//返回从1开始的映射
}
//从下标为1的位置输入数据
for(int i = 1; i <= n; i++){
cin >> arr[i];
temp[i] = arr[i];
}
sort(temp + 1, temp + n + 1); //对副本进行排序
//排序后去重,并获取去重后副本长度
int len = unique(temp + 1, temp + n + 1) - (temp + 1);
int find(int x) // 找到第一个大于等于x的位置
{
int l = 1, r = len; //副本离散化操作后数据下标从1开始,所以l = 1
while (l < r)
{
int mid = l + r >> 1;
if (temp[mid] >= x) r = mid;
else l = mid + 1;
}
return r; // 映射到1, 2, ...n
}
模版二
对于个别题目,有时会把相同元素根据输入顺序离散化为不同的数据。
此时利用lower_bound()函数实现略显困难,毕竟去重后的数据使用lower_bound()函数查找相同元素只会有一个返回值,则对于该问题需要改变思路:
- 创建原数组副本,同时记录每个元素出现的位置。
- 对副本进行从小到大排序,当数据相同时,按照出现顺序从小到大排序。
- 将离散化后的数据放回原数组。
struct Data {
int idx, val;
//后面的const表明调用函数的成员对象不能被修改
//const Data& o的const表明对象o的成员不能被修改
bool operator<(const Data& o) const {
if (val == o.val)
return idx < o.idx; // 当值相同时,先出现的元素离散化后的值更小
return val < o.val;
}
} tmp[maxn]; // 也可以使用 std::pair
for (int i = 1; i <= n; ++i) tmp[i] = (Data){i, arr[i]};
sort(tmp + 1, tmp + n + 1);
for (int i = 1; i <= n; ++i) arr[tmp[i].idx] = i;
例题演示以下是调整后的文字格式:
题目描述
假定有一个无限长的数轴,数轴上每个坐标上的数初始值都是 0。
现在,我们首先进行 (n) 次操作,每次操作将某一位置 (x) 上的数加 (c)。
接下来,进行 (m) 次询问,每个询问包含两个整数 (l) 和 (r),你需要求出在区间 ([l,r]) 之间的所有数的和。
输入格式
- 第一行包含两个整数 (n) 和 (m)。
- 接下来 (n) 行,每行包含两个整数 (x) 和 (c)。
- 再接下来 (m) 行,每行包含两个整数 (l) 和 (r)。
输出格式
- 共 (m) 行,每行输出一个询问中所求的区间内数字和。
数据范围
输入样例
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例
8
0
5
解题思路:
对要操作的数据和区间[l,r]进行离散化,然后对需操作的数据进行+c操作,之后进行求前缀和s[i],最后对区间进行操作,获取所需操作区间离散化后的值,并求取前缀和即可。
图示中的alls对应代码中的arr
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pa;
//根据题意,操作数据最多有300000个
const int N=1e6+10;
int a[N],s[N];
vector<int> arr; //定义arr存储需要进行离散化的数据,并对其进行离散化操作
vector<pa> arrs,query;//arrs存储需要操作的数据(x, c),query存储区间[l, r]
int main(){
int n,m;
cin>>n>>m;
//读取并存储操作(x, c)以及x
for(int i=0;i<n;i++){
int x,c;
cin>>x>>c;
arrs.push_back({x,c});//对操作(x, c)进行存储
arr.push_back(x);//存储数轴坐标x
}
//读取操作区间[l, r],并将操作区间和数轴坐标l、r分别进行存储
for(int i=0;i<m;i++){
int l,r;
cin>>l>>r;
query.push_back({l,r});
arr.push_back(l);
arr.push_back(r);
}
//对数据进行排序
sort(arr.begin(),arr.end());
//堆排序后数据进行去重操作
arr.erase(unique(arr.begin(),arr.end()),arr.end());
//获取离散化后数据对应的下标位置,并存入对应需操作的数据c
for(auto i:arrs){
auto x=lower_bound(arr.begin(),arr.end(),i.first)-arr.begin();
a[x]+=i.second;
}
//前缀和操作
//因为前面操作的数组a利用lower_bound()函数实现,所以下标从0开始
//所以求取前缀和操作中对数组a,应该是加上a[i-1]从0开始进行加
//但是前缀和数组s的下标依然是从1开始
for(int i=1;i<=arr.size();i++)
s[i]=s[i-1]+a[i-1];
//获取操作区间l、r离散化后所对应的下标
for(auto i:query){
int l=lower_bound(arr.begin(),arr.end(),i.first)-arr.begin();
int r=lower_bound(arr.begin(),arr.end(),i.second)-arr.begin();
//arr的下标从0开始,而前缀和s下标从1开始
//所以对于所需操作的r值应加1等价到前缀和s的下标
//若不理解,建议自行作图
cout<<s[r+1]-s[l]<<endl;
}
return 0;
}