本题用到了离散化的思想。
离散化的本质是建立了一段数列到自然数之间的映射关系(value -> index),通过建立新索引,来缩小目标区间,使得可以进行一系列连续数组可以进行的操作比如二分,前缀和等…
离散化首先需要排序去重:
1. 排序:sort(alls.begin(),alls.end())
2. 去重:alls.earse(unique(alls.begin(),alls.end()),alls.end());
unique()函数的底层原理
vector<int>::iterator unique(vector<int> &a) {
int j = 0;
for (int i = 0; i < a.size(); ++i) {
if (!i || a[i] != a[i - 1])//如果是第一个元素或者该元素不等于前一个元素,即不重复元素,我们就把它存到数组前j个元素中
a[j++] = a[i];//每存在一个不同元素,j++
}
return a.begin() + j;//返回的是前j个不重复元素的下标
}
由于本题可能有多组数据是针对同一个数组下标操作的,因此我们可以将所有用到的数组下标装在一个下标容器alls内去重,然后再逐一为相同的数组下标增加数值c,再通过对应前缀和相减求得区间 l~r 之间的数的值。
需要注意加深理解的点:
为什么要把lr加到alls容器里?答:l和r是原数轴上的下标,alls是所有需要用到的下标,而不是单单那些非0的点的下标,求区间和的时候有可能会用到那些非0点。二分查找的过程就是将这些有序原下标转化为连续的1,2,3,4,5.....下标的过程,这个下标是a数组的下标,a数组用来存储每次+c的数据。每次询问l和r之间的区间和,只需要计算[l,r]之间非零的数之和,我们最后计算用的前缀和s是a数组的前缀和,也就是需要经过原下标(l与r)—>离散后下标的转化,我们访问[l,r]区间也就是访问a数组的一个区间。
其他重点见注释。
完整代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N=300010;
typedef pair<int,int> PII;
vector<int> alls;//存放下标
vector<PII>add,query;//存放操作的容器
int a[N],s[N];
int find(int x){//二分算法,把原下标映射成离散化下标
int l=0,r=alls.size()-1;
while(l<r){//查找大于等于x的最小的值的下标
int mid=l+r>>1;
if(alls[mid]>=x) r=mid;
else l=mid+1;
}
return r+1;//因为使用前缀和,其下标要+1可以不考虑边界问题
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
while(n--){
int x,c;
scanf("%d%d",&x,&c);
add.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);//注意lr边界也要放入alls
}
sort(alls.begin(),alls.end());//排序
alls.erase(unique(alls.begin(),alls.end()),alls.end());//去重
for(auto item:add){//处理插入
int x=find(item.first);
a[x]+=item.second;
}
for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];//预处理前缀和
for(auto item:query){//处理询问
int l=find(item.first),r=find(item.second);//离散化
printf("%d\n",s[r]-s[l-1]);//计算区间和
}
}