其他分享
首页 > 其他分享> > ACWing802. 区间和(前缀和+二分+离散化)

ACWing802. 区间和(前缀和+二分+离散化)

作者:互联网

题目链接:https://www.acwing.com/problem/content/804/

1.思路

这题一看感觉和前面的前缀和那题一样吗?结果一看,完蛋,发现不同,无限长的数轴,接着往下看,发现其实要计算的点远远小于数轴长度,就知道要用离散化了。所以这题大概的思路就是先离散化数据,然后再利用前缀和计算出区间的总和。这里代码很长,但是我已经做了很多优化了,在不增加理解难度的条件下使代码短一点。下面我来讲一下详细思路,代码虽然长,但是理解了实际上不是很难,离散化也没那么高端。仔细看,好好理解。

首先,由于我们只用到xlr这三个点,根据题目范围发现三个点加起来最多也就300000,所以先创建一个数组a,用于存储离散化之后的点。但是我们其实只要存储每个点只要存储一次就行,所以就先用set存储,set可以去除重复存储的点。我们可以用add集合来记录xc(还没离散化之前只能先用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.复杂度分析

标签:ACWing802,二分,arr,set,下标,前缀,int,add,new
来源: https://blog.csdn.net/qq_44713772/article/details/116272435