ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-04-29 16:58:33  阅读:188  来源: 互联网

标签:ACWing802 二分 arr set 下标 前缀 int add new


题目链接: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.复杂度分析

  • 时间复杂度: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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有