离散化及模板
离散化及模板
1.离散化的定义及问题引出
在讲解之前,需要注意:我们这里的离散化特指整数离散化,而且是有序(保序)的离散化。这里的有序指的就是原数组A是有序的。保序指的就是原数组A被离散化之后,顺序不发生变化。
在提出上述内容后,我们来阐述一下离散化的定义。假如有这样的一个应用场景:我们需要将10的5次方的数进行打表,目的是统计它们出现的次数。请注意:这些个数的范围均是0-10的9次方。如果我们要开一个数组来直接进行打表的话(按照数组的值),数组的长度就需要是10的9次方,这样就极大的增高了空间复杂度。
为此,离散化就是为了解决这样的问题。我们可以将这些数(10的5次方个)进行映射,一一映射为:0,1,2,...,n-1个数字(这里的n为10的5次方个)。(进行下标映射)映射完数字之后,再开数组进行打表,我们就可以发现,数组的长度只需要10的5次方即可,而不需要10的9次方。这样就极大的减少了空间复杂度。
我们将上述映射的过程叫做离散化。我们可以上图一个实际的例子来理解离散化。
对于上述的过程,有两个问题需要解决:
1. 在离散化的过程中,如果原数组A有重复元素如何处理?
2. 离散化的过程如何实现?离散化后的值如何计算?如何可以在保证时间复杂度尽可能小的情况下快速实现离散化?
在接下来的阐述中,我们来依次解答这些问题。
2.离散化问题的解决方案
对于离散化的第一个问题,我们只需要对原数组A中相同的元素进行去重即可。对于这个操作我们可以利用c++的vector容器来实现。
对于离散化的第二个问题,我们可以将原数组中的值x映射为下标,那么如何寻找值x所对应的下标呢?有两个解决方案,第一个我们可以直接遍历,这样的话时间复杂度就是O(n)。第二个,由于原数组A是有序的,因此我们可以采用二分法来寻找下标。这样的话,时间复杂度O(log2n)。我们在这里采用二分法的方式来寻找下标。
这样的话,就完美解决了离散化的问题。
3. 离散化的模板
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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;
}
//需要注意,如果是r,则映射到0,1,2,...,n-1。
//具体是r还是r+1,需要具体问题具体分析。
return r + 1; // 映射到1, 2, ...n
}
4. 离散化例题
https://www.acwing.com/problem/content/804/
在讲述这道题之前,我们需要了解一下在什么样的情况下,可以应用离散化这个算法。大体来说,特征如下:
1. 数值范围很大,但是数的个数很少(需要用到的数很少)。
2. 如果打表过长,可以使用离散化来进行空间复杂度上的优化。
这道题是一道很典型的离散化的例题。如果这道题的数值范围在10的5次方的话,那么这道题是可以采用前缀和来求解的。具体步骤如下:
1. 以数值为下标,来开辟数组,完成题目中的第一个操作。
2. 构造前缀和数组。
3. 利用构造的前缀和数组来完成题目的第二个操作。
但是这道题的数值范围过大,因此直接按照数值范围开辟数组是不可行的。因此,我们就需要将数值离散化为其下标,缩小范围。离散化之后再开辟数组,就可以采用前缀和来求解了。
这道题的数值范围很大,但是我们所需要进行离散化的数却很少。总共说来包含:
1. 我们需要对题目中的x进行离散化,x的个数为10的5次方。
2. 我们需要对题目中的l和r也进行离散化,l和r的个数分别为10的5次方。
综合下来,我们需要离散化的个数为:3*10的5次方。因此,我们需要开辟一个离散化后的数组A,数组大小为:300010。以及前缀和数组B,大小同上。
因此,将上述的数添加到vector容器,进行离散化即可。
最后,我们也需要记录一下操作,离散化之后进行。
这里需要说一下额外的内容:离散化过程中的去重操作是怎么实现的?
具体可以观看上述的图片。采用双指针算法即可。
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 300010;
//离散化后的数组
int a[N];
//前缀和数组
int s[N];
//用于离散化的vector容器
vector<int> alls;
//用于记录操作的pair数对
vector<pair<int,int>> adds,query;
//3. 代表将x进行离散化的函数
int find(int x){
int l = 0;
int 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;
}
int main(){
int n,m;
scanf("%d %d",&n,&m);
while(n--){
int x,c;
scanf("%d %d",&x,&c);
//记录操作
adds.push_back({x,c});
//把将要进行离散化的坐标放在容器中
alls.push_back(x);
}
while(m--){
int l,r;
scanf("%d %d",&l,&r);
//记录操作
query.push_back({l,r});
//把将要进行离散化的坐标放在容器中
alls.push_back(l);
alls.push_back(r);
}
//离散化
//1. 进行排序
sort(alls.begin(),alls.end());
//2. 进行去重
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//离散化到数组a后进行操作
for(int i=0;i<adds.size();i++){
//进行离散化
int result = find(adds[i].first);
a[result] += adds[i].second;
}
//根据数组a求前缀和数组,以便于之后进行操作
//初始化前缀和数组
for(int i=1;i<=N;i++){
s[i] = s[i-1] + a[i];
}
//继续操作
for(int i=0;i<query.size();i++){
//将l和r分别进行离散化
int lresult = find(query[i].first);
int rresult = find(query[i].second);
printf("%d\n",s[rresult] - s[lresult - 1]);
}
return 0;
}
作者:gao79138
链接:https://www.acwing.com/
来源:本博客中的截图、代码模板及题目地址均来自于Acwing。其余内容均为作者原创。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现