假定有一个无限长的数轴,数轴上每个坐标上的数都是0。
现在,我们首先进行 n 次操作,每次操作将某一位置x上的数加c。
接下来,进行 m 次询问,每个询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和。
输入格式
第一行包含两个整数n和m。接下来 n 行,每行包含两个整数x和c。
再接下里 m 行,每行包含两个整数l和r。
输出格式
共m行,每行输出一个询问中所求的区间内数字和。
数据范围
−109 ≤ 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
离散化算法分析
- 为什么要使用离散化:因为存储的下标实在太大了,如果直接开这么大的数组,根本不现实,第二个原因,本文是数轴,要是采用下标的话,可能存在负值,所以也不能,所以有人可能会提出用哈希表,哈希表可以吗?答案也是不可以的,因为哈希表不能像离散化那样缩小数组的空间,导致我们可能需要从
1e-9
遍历到1e9
(此处的含义就是假如我们需要计算1e-9
和1e9
区间内的值,那我们需要从前到后枚举,无论该值是否存在),因为哈希表不能排序,所以我们一般不能提前知道哪些数轴上的点存在哪些不存在,所以一般是从负的最小值到正的最大值都枚举一遍,时间复杂度太高,于是就有了本题的离散化。
- 读输入,将每次读入的
x
和c
放进到add
中,将每次读入的位置x
到alls
中,将每次读入的l
和r
存进到query
中。 - 排序、去重。
- 通过遍历
add
,完成在离散化的数组映射到的a
数组中进行加上c
的操作(用到find
函数)。 - 初始化
s
数组。(这里使用前缀和) - 通过遍历
query
,完成求区间[l,r]
的和。
问题
- 为什么要在
alls
中需要把询问的边界也加入进alls
中进行离散化?
- 首先要明确
alls
中存放的是位置而不是值,也就是存放的是x
而不是c
。 - 因为再求区间和的时候,我们提前分析到可以使用前缀和来做,求前缀和就需要下标
l
和r
,如果不加入l
和r
到alls
中的话,第5步中遍历时query
就没有办法通过输入的l
和r
去访问a
或者list
。因为find
函数就是输入映射前的下标,返回在alls
中的下标+1
。
- 为什么要排序和去重?
- 首先要明确
find
函数的功能,输入一个离散数组的位置(映射前的位置)x
返回连续数组的位置+1
(映射后的位置+1
)。+1
的目的是为了求区间和时少一步下标为0
的判断。 - 排序很好理解,因为在
find
函数中是使用了二分来查找x
在alls
中的下标+1
,想要使用二分alls
就必须具有某种性质这里就可以找一个最简单的办法使它单调(二分!=单调性)。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException {
int N = 300010;
int[] a = new int[N];
int[] s = new int[N];
List<Integer> allS = new ArrayList<>();//存储所有待离散化的值
List<PIIs> add = new ArrayList<>();//存储所有对应位置增加的值以及增加多少
List<PIIs> query = new ArrayList<>();//存储每次查询的左右边界
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String[] str = reader.readLine().split(" ");
int n = Integer.parseInt(str[0]); // n次操作
int m = Integer.parseInt(str[1]); // m次询问
//存储增加操作
for (int i = 0; i < n; i++) {
String[] str1 = reader.readLine().split(" ");
int x = Integer.parseInt(str1[0]);
int c = Integer.parseInt(str1[1]);
add.add(new PIIs(x, c));//将所有增加操作记录到add中
allS.add(x);//将x记录到alls中
}
//存储询问的边界
for (int i = 0; i < m; i++) {
String[] str2 = reader.readLine().split(" ");
int l = Integer.parseInt(str2[0]);
int r = Integer.parseInt(str2[1]);
query.add(new PIIs(l, r));
allS.add(l);
allS.add(r);
}
reader.close();
//先排序再去重
Collections.sort(allS);
int unique = unique(allS);
allS = allS.subList(0, unique);
//处理插入
for (PIIs item : add) {
int x = find(item.getFirst(), allS);
a[x] += item.getSecond();
}
//预处理前缀和
for (int i = 1; i <= allS.size(); i++) {
s[i] = s[i - 1] + a[i];
}
//处理询问
for (PIIs item : query) {
int l = find(item.getFirst(), allS);
int r = find(item.getSecond(), allS);
System.out.println(s[r] - s[l - 1]);
}
}
//alls中待离散化的值进行去重去重
public static int unique(List<Integer> list) {
int j = 0;
for (int i = 0; i < list.size(); i++) {
if (i == 0 || list.get(i) != list.get(i - 1)) {
list.set(j++, list.get(i));
}
}
//a[0]到a[j - 1]是所有a中不重复的数
return j;
}
//使用二分来找到x对应离散化的位置
public static int find(int x, List<Integer> allS) {
int l = 0;
int r = allS.size() - 1;
while (l < r) {
int mid = l + r >> 1;
if (allS.get(mid) >= x) {
r = mid;
} else {
l = mid + 1;
}
}
return r + 1;//返回r + 1是因为离散从1开始的下标,为了后面求前缀和不越界
}
}
//构造二元组PII
class PIIs implements Comparable<PIIs>{
private int first;
private int second;
public int getFirst() {
return first;
}
public int getSecond() {
return second;
}
public PIIs(int first, int second) {
this.first = first;
this.second = second;
}
@Override
public int compareTo(PIIs o) {
return Integer.compare(first, o.first);
}
}