算法基础 (13)离散化

假定有一个无限长的数轴,数轴上每个坐标上的数都是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-91e9区间内的值,那我们需要从前到后枚举,无论该值是否存在),因为哈希表不能排序,所以我们一般不能提前知道哪些数轴上的点存在哪些不存在,所以一般是从负的最小值到正的最大值都枚举一遍,时间复杂度太高,于是就有了本题的离散化。
  1. 读输入,将每次读入的xc 放进到add中,将每次读入的位置xalls中,将每次读入的lr 存进到query中。
  2. 排序、去重。
  3. 通过遍历add,完成在离散化的数组映射到的a数组中进行加上c的操作(用到find函数)。
  4. 初始化s数组。(这里使用前缀和)
  5. 通过遍历query,完成求区间[l,r]的和。

问题

  1. 为什么要在alls中需要把询问的边界也加入进alls中进行离散化?
  • 首先要明确alls中存放的是位置而不是值,也就是存放的是x而不是c
  • 因为再求区间和的时候,我们提前分析到可以使用前缀和来做,求前缀和就需要下标lr,如果不加入lralls中的话,第5步中遍历时query就没有办法通过输入的lr去访问a或者list。因为find函数就是输入映射前的下标,返回在alls中的下标+1
  1. 为什么要排序和去重?
  • 首先要明确find函数的功能,输入一个离散数组的位置(映射前的位置)x返回连续数组的位置+1(映射后的位置+1)。+1的目的是为了求区间和时少一步下标为0的判断。
  • 排序很好理解,因为在find函数中是使用了二分来查找xalls中的下标+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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值