ACWing802. 区间和(前缀和+二分+离散化)
作者:互联网
题目链接:https://www.acwing.com/problem/content/804/
1.思路
这题一看感觉和前面的前缀和那题一样吗?结果一看,完蛋,发现不同,无限长的数轴,接着往下看,发现其实要计算的点远远小于数轴长度,就知道要用离散化了。所以这题大概的思路就是先离散化数据,然后再利用前缀和计算出区间的总和。这里代码很长,但是我已经做了很多优化了,在不增加理解难度的条件下使代码短一点。下面我来讲一下详细思路,代码虽然长,但是理解了实际上不是很难,离散化也没那么高端。仔细看,好好理解。
首先,由于我们只用到x
和l
,r
这三个点,根据题目范围发现三个点加起来最多也就300000,所以先创建一个数组a
,用于存储离散化之后的点。但是我们其实只要存储每个点只要存储一次就行,所以就先用set存储,set可以去除重复存储的点。我们可以用add集合来记录x
和c
(还没离散化之前只能先用add
集合来暂时存储),同时把下标x
加入set
集合,后面我们可以用query
集合来存储要访问的区间,并且同时把两个下标加入set
集合。
初始化工作全部完成,接下来就要处理离散化了。我们先要排序所有存储的下标,但是set
集合不好排序,所以我们把set
集合转换为List
集合,然后用List
集合排序,到这里离散化就差不多完成了。接下来我们就可以把之前要加入的值加入到离散化之后的数组中a
,加入所有之后,就可以开始计算前缀和了,用s
数组计算前缀和。
最后,就可以通过二分查找,查找出区间左边界和右边界,通过前缀和来计算区间总和。
大功告成!代码和思路都挺麻烦的,好好去理解(我刚刚想了一个其他的方法,花了一下午去实现,结果超时了)。你理解了别人不理解的方法,你就比别人更加强了一点,加油!
2.代码
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = 300010; //所有不重复的点最多为300000
int[] a = new int[N]; //把所有用到的点离散化到a数组
int[] s = new int[N]; //计算离散化之后,前缀和的数组,用于计算区间的总和
Set<Integer> set = new HashSet<>(); //去重
List<int[]> add = new ArrayList<>(); //记录位置 x 上的数加 c
List<int[]> query = new ArrayList<>(); //询问包含的两个整数 l 和 r
int n = in.nextInt();
int m = in.nextInt();
int[] arr;
for(int i = 0; i < n; i++) {
arr = new int[2]; //一定要在循环里面new,如果在外面new的话list保存的还是同一个对象,因为地址相同
arr[0] = in.nextInt(); //题目中的x,位置
arr[1] = in.nextInt(); //题目中的c,要加的值
add.add(arr); //记录位置 x 上的数加 c
set.add(arr[0]); //记录所有不重复的下标
}
for(int i = 0; i < m; i++) {
arr = new int[2];
arr[0] = in.nextInt(); //题目中的l,要查询的左区间
arr[1] = in.nextInt(); //题目中的r,要查询的右区间
query.add(arr); //加入最后要访问的集合中
set.add(arr[0]); //记录所有不重复的下标
set.add(arr[1]); //记录所有不重复的下标
}
List<Integer> all = new ArrayList<>(set); //由于set以及去重且把所有点都统计过了,为了之后的排序,所以要用list集合
Collections.sort(all); //Collections工具类排序集合
for(int[] i : add) { //遍历集合
int x = find(i[0], all); //使用二分查找找到要加的值下标x
a[x] += i[1]; //在x下标加上c
}
for(int i = 1; i <= all.size(); i++) //计算前缀和
s[i] = s[i - 1] + a[i];
for(int[] i : query) { //遍历要查找的区间,输出结果
int l = find(i[0], all); //找到左区间
int r = find(i[1], all); //找到右区间
System.out.println(s[r] - s[l - 1]); //前缀和计算结果
}
}
public static int find(int x, List<Integer> all) { //二分查找,all中没有重复元素,并且要查找的值必定存在
int l = 0, r = all.size() - 1;
while (l < r) {
int mid = l + r >> 1;
if (all.get(mid) >= x) r = mid;
else l = mid + 1;
}
return r + 1; //加一是因为数组s的下标是从1开始的
}
}
3.复杂度分析
- 时间复杂度:O(n+2∗m)log(n+2∗m),如果set中所有元素都不重复,那么set会存储(n+2∗m)个元素,之后排序会花掉(n+2∗m)log(n+2∗m)的时间复杂度
- 空间复杂度:O(n+2∗m)
标签:ACWing802,二分,arr,set,下标,前缀,int,add,new 来源: https://blog.csdn.net/qq_44713772/article/details/116272435