开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
离散化
解释说明
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l和 r,你需要求出在区间 l,r 之间的所有数的和。
刚看到本题的读者就会想,这题不是很简单嘛,简单一个for循环进行相加不就行了吗。但要注意一下题目数据范围 -1e9≤x≤1e9,所以可想结果会超时,所以此时我们就需要离散化
离散化简单来说,就是新建一个数组进行储存仅需要的数据,如下图 我们会得到一个[x(添加数据位置),c(数据)],那么除此之外的区间全为0,那么我们就可以再建一个数组,存储实际的数字 (此时就要注意,离散化后的下标就以新创建的为主了)
这里新数组下标也从0开始,写错了🥲
同样 最后求区间,也新建一个数组,进行存储左右端点
2.实战演练
1.准备条件
1)数组确定
根据题目我们可以判断需要设定四个数组
====离散化所需数组
1.存储下标位置的数组
2.存储所添加数据的数组
3.存储区间和的区间数组
====求区间所需数组
看到求区间第一想到的就是前缀和,如果有帅读者没接触,可以看我之前发的文章小白学算法之前缀和篇 - 掘金 (juejin.cn)
所以需要两个数组
1.数据数组
2.前缀和数组
2)判断数据边界
数据范围 1≤n,m≤1e5,
所以我们存储下标位置的数组,所需要开辟的最大空间为
n个x,m个l,m个r
共3e5
const int N=3e5+10 定义时习惯加上一点,防止越界等问题
//求前缀和需要的数组
//s前缀和数组,a数据数组
int s[N],a[N];
//离散化需要的数组
//alls 坐标数组
vector<int>alls;
//add添加数据数组{下标,数据}
//query区间数组{左端点,右端点}
vector<pair<int,int>>add,query;
3)查找函数
当我们离散化后,所得到的下标不是原来的下标,所以我们怎样通过原来的数据来得到离散后的数组下标呢
这里自定义一个查找函数(二分),没了解过的帅读者可以看完原来的文章
小白学算法之折半(二分)查找 - 掘金 (juejin.cn)
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;
}
//+1 使得离散化的下标是从1开始的
return r+1;
}
2.存储数据
初始条件已经准备好了,那么就开始存储数据
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x,c;
cin>>x>>c;
//alls 存储过程中所有的下标
alls.push_back(x);
//add 存储初始化的下标和增加数
add.push_back({x,c});
}
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
//存入区间范围
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
}
到这步就存储结束了吗,当然不是
1)排序
我们知道已经定义一个二分查找函数,那么二分查找函数最主要的一点------顺序查找!,需要进行一个排序
2)去重
我们存入了两部分的区间下标,我们需要考虑就是是否有重复
sort(alls.begin(),alls.end());
//unique函数 功能是将数组中相邻的重复元素去除。然而其本质是将重复的元素移动到数组的末尾,
//最后再将迭代器指向第一个重复元素的下标。然后去掉剩下重复的元素
alls.erase(unique(alls.begin(),alls.end()),alls.end());
3.求解
//得到新的离散数组,进行数的插入
for(auto temp:add)
{
//此时根据原来数组元素查找离散数组的下标
int x=find(temp.first);
//进行添加数据
a[x]+=temp.second;
}
//求前缀和
for(int i=1;i<=alls.size();i++)
{
s[i]=s[i-1]+a[i];
}
//最后进行区间和的运算
for(auto temp:query)
{
//取出区间下标
int l=find(temp.first);
int r=find(temp.second);
cout<<s[r]-s[l-1]<<endl;
}
最后请各位记住离散化的本质是映射,将间隔很大的点,映射到相邻的数组元素中。减少对空间的需求,也减少计算量。
4.完整代码
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=3e5+10;
int n,m;
int a[N];
int s[N];
vector<int>alls;
vector<pair<int,int>>add,query;
//二分法求离散化的下标
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;
}
//+1 使得离散化的下标是从1开始的
return r+1;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x,c;
cin>>x>>c;
//alls 存储过程中所有的下标
alls.push_back(x);
//137
//add 存储初始化的下标和增加数
add.push_back({x,c});
}
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
//存入区间范围
query.push_back({l,r});
//137134678
alls.push_back(l);
alls.push_back(r);
}
//去重,去重前先进行排序
sort(alls.begin(),alls.end());
//unique 返回去重后的尾部迭代器,然后删掉剩下的内容
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//得到新的离散数组,进行数的插入
for(auto temp:add)
{
//得到初始情况下的位置
int x=find(temp.first);
a[x]+=temp.second;
}
//求前缀和
for(int i=1;i<=alls.size();i++)
{
s[i]=s[i-1]+a[i];
}
//最后进行区间和的运算
for(auto temp:query)
{
//取出区间下标
int l=find(temp.first);
int r=find(temp.second);
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}