2020/10/6 Acwing-离散化

题目

假定有一个无限长的数轴,数轴上每个坐标上的数都是0。
现在,我们首先进行 n 次操作,每次操作将某一位置x上的数加c。
接下来,进行 m 次询问,每个询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和。
输入格式
第一行包含两个整数n和m。
接下来 n 行,每行包含两个整数x和c。
再接下里 m 行,每行包含两个整数l和r。
输出格式
共m行,每行输出一个询问中所求的区间内数字和。
数据范围
−109≤x≤109,
1≤n,m≤105,
−109≤l≤r≤109,
−10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5

题目分析

本题是目前来说比较综合的题目.

  1. 因为整个数轴并没有填满,更多的是空的值,所以使用原数轴做角标会很浪费,因此需要将原数轴上的数做一个映射,到一个新的数组中,从而将降低资源浪费节约时间.
  2. 这里使用的容器是ArrayList,可以使用集合工具类对容器进行排序,但是很有可能容器里有重复的数,去重操作使用的是双指针思想.
  3. 在建立映射时,原数轴上的数按顺序排布对应新数组上的数,给出任何一个原数轴上的数应该立马得到他在新数组中的位置.这个查找方式使用的是二分查找.
  4. 在将数组完成映射后,原数组上的部分和就转化成了新数组的部分和,在求部分和时使用前缀和数组,这样在求和时可以实现O(1)的时间复杂度.

算法分析

在这里插入图片描述
我们关心的,只是他们在原数轴上的大小顺序,然后将排序之后的结果作为新的index,而不去动他所对应的值.
在找到了新坐标后,为了方便我们对新数组赋值,使用了自建类Pairs,类中只定义了另两个成员变量first,second,方便我们操作成对出现的值(如写入的x和n;查询的l和r)

源代码

import java.util.*;
class Main{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int m=sc.nextInt();
        int N=300010;
        int[] a=new int[N];//映射后的数组
        int[] s=new int[N];//前缀和数组
        List<Integer> alls=new ArrayList<>();
        List<Pairs> adds=new ArrayList<>();
        List<Pairs> querys=new ArrayList<>();
        while(n-->0){
            int x=sc.nextInt();
            int c=sc.nextInt();
            adds.add(new Pairs(x,c));
            alls.add(x);
            
        }
        while(m-->0){
            int l=sc.nextInt();
            int r=sc.nextInt();
            alls.add(l);
            alls.add(r);
            querys.add(new Pairs(l,r));
        }
        Collections.sort(alls);
        int unique=unique(alls);
        alls=alls.subList(0,unique);//现在数组中全部是不重复的元素
        for(Pairs ele:adds){  //将映射数组填充
            int index=seek(alls,ele.first);
            a[index]+=ele.second;
        }
        
        for(int i=1;i<=alls.size();i++){ //前i个数的缀和数组(i从1开始)
            s[i]=s[i-1]+a[i];
        }
        
        for(Pairs ele:querys){
            int l=seek(alls,ele.first);
            int r=seek(alls,ele.second);
            int res=s[r]-s[l-1];
            System.out.println(res);
        }
        
        
    }
    static int unique(List<Integer> al){//将已排序数组里的重复元素去掉 整体是相当于两个指针一个挑选元素,一个确定位置
        int j=0; //j表示要插入元素的位置
        for(int i=0;i<al.size();i++){ //i表示当前选择的元素
            if(i==0||al.get(i)!=al.get(i-1)){
                al.set(j,al.get(i));
                j++;
            }
        }
        return j;//返回不重复的最后一个索引
    } 
    
    static int seek(List<Integer> list,int x){
        int l=0;
        int r=list.size()-1;
        while(l<r){
            int mid=l+r>>1;
            if(list.get(mid)>=x){
                r=mid;
            }else{ l=mid+1;}
        }
        return l+1;//将数组的每一个角标都加1,这样数组就是从角标为1开始的
        
    }
}
class Pairs{
    int first;
    int second;
    public Pairs (int first,int second){
        this.first=first;
        this.second=second;
    }
}

细节说明

Q:为什么a[]/s[]数组的容量是300000?
A:首先确认a数组存储的是映射后的数组,s数组存储的是a的前缀和,因此s的容量一定和a相同.那么a为什么是呢? 思考都有哪些需要映射: 数轴上的点一定需要映射,因为数轴上加了n个点,所以要映射n次;同时,查询的l,r也一定要映射,否则,我们无法对数组进行查询(只不过映射的值为0而已).查询m次,因此要映射2m个点,总共映射n+2m,最多为300000.

Q:为什么查询函数返回值为j+1而不是j?
A:因为查询到的其实本质上是从0开始的角标,而在a[]中,第一个数的角标其实是1,因此直接返回的值+1就可以对应他在a[]中的位置了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值