题目描述
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围
−10^9 ≤ x ≤ 10^9,
1 ≤ n,m ≤ 10^5,
−10^9 ≤ l ≤ r ≤ 10^9,
−10000 ≤ c ≤ 10000
样例
输入
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出
8
0
5
离散化
分析
因为x的数据范围是在-1e9到1e9之间,无法建立数组进行计算。
但是因为数轴上有效的点数在3e5以下(包含加操作的1e5个点和询问操作的2e5个点),可以将x映射到一个新的数轴中,去除所有无效点。
例如:
下标 1 7 9 11 -> 1 2 3 4
数据 1 7 9 11 -> 1 7 9 11
使用二分法寻找原数轴上点映射到新数轴上的位置(find函数)
最后应用前缀和处理询问
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=300010;//最坏情况,所有元素无重复n+2m(需要加的值和询问的两个端点)
typedef pair<int,int> PII;
int n,m;
vector<int> alls;//映射后的向量
vector<PII> add;//需要进行的加操作
vector<PII> query;//需要回答的询问
int a[N];//映射后的新数组
int s[N];//储存前缀和
//给一个-1e9到1e9的数,映射到alls上
//二分查找
int find(int x){
int l=0,r=alls.size()-1;//alls已经去重,此处不能用n
while(l<r){
int mid=l+r>>1;
if(alls[mid]>=x) r=mid;
else l=mid+1;
}
return r+1;//之后需要进行前缀和操作,返回下标需要加1
}
int main(){
//1.处理输入
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
int x,c;
scanf("%d%d",&x,&c);
add.push_back({x,c});
alls.push_back(x);//以x为依据映射
}
for(int i=0;i<m;i++){
int l,r;
scanf("%d%d",&l,&r);
query.push_back({l,r});
alls.push_back(l);//注意此处需要将l和r也加入到alls集合中
alls.push_back(r);
}
//2.为alls去重
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//3.在映射位置执行加操作
for(int i=0;i<n;i++){
int x=add[i].first,c=add[i].second;
a[find(x)]+=c;
}
//4.预处理前缀和
for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];
//5.处理询问
for(int i=0;i<m;i++){
int l=find(query[i].first);
int r=find(query[i].second);
printf("%d\n",s[r]-s[l-1]);
}
return 0;
}