离散化

116 阅读2分钟
  • 离散化的本质是建立了一段数列到自然数之间的映射关系(value -> index),通过建立新索引,来缩小目标区间,使得可以进行一系列连续数组可以进行的操作比如二分,前缀和等…

Snipaste_2023-02-25_22-50-21.png

模板

  • C++
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}
  • Java
List<Integer> alls = new ArrayList<>();
alls = alls.stream().distinct().sorted().collect(Collectors.toList());

public static int find(int x) {
    int l = 0, r = alls.size() - 1;
    while (l < r) {
        int mid = l + r >> 1;
        if (alls[mid] >= x) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return l + 1;
}

练习

01 区间和

  • 题目

Snipaste_2023-02-26_15-06-24.png

  • 分析
    • 为什么不用Map集合来存储数据?
      • Map集合在存储数据时底层会根据键的值来计算数据的哈希值从而确定数据的存放位置,如果当前要存储的数据的键和已经存进去的某个数据的键一样或者计算出来的哈希值一样时(哈希碰撞),那么当前数据不会被存入Map集合中,而是被直接丢弃
    • 思路
      • 由于区间太大,所以使用离散化的思想进行简化
      • 首先题目中遇到的每一个区间上的数(坐标)添加到alls集合中
      • 将alls集合去重和排序
      • 将alls中存储的坐标离散化到数组a中,使得每个坐标都对应着数组a中的一个下标,这一操作由find方法完成
      • 再将add集合中要在对应坐标上加上的数通过离散化加到数组a对应的位置上
      • 这样的话add中的坐标和query中的坐标都能在数组a的索引上一一对应,其中add中坐标加上的数变成了坐标对应的数组索引中存储的值,而query中的坐标对应的数组索引中存储的值为0,并不影响最后的计算
      • 计算前缀和数组S
      • 每一次查询的坐标区间通过离散化转化为数组a的索引区间
      • 最后就转化为了前缀和问题
  • 题解
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

public class discretization1 {
    public static final int N = 300010;
    public static int n, m;
    public static List<Pair> add = new ArrayList<>();
    public static List<Pair> query = new ArrayList<>();
    public static List<Integer> alls = new ArrayList<>();
    public static int[] a = new int[N];
    public static int[] S = new int[N];

    public static class Pair {
        int first;
        int second;

        public Pair(int first, int second) {
            this.first = first;
            this.second = second;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for (int i = 0; i < n; i++) {
            String[] str2 = br.readLine().split(" ");
            add.add(new Pair(Integer.parseInt(str2[0]), Integer.parseInt(str2[1])));
            alls.add(Integer.parseInt(str2[0]));
        }
        for (int i = 0; i < m; i++) {
            String[] str3 = br.readLine().split(" ");
            query.add(new Pair(Integer.parseInt(str3[0]), Integer.parseInt(str3[1])));
            alls.add(Integer.parseInt(str3[0]));
            alls.add(Integer.parseInt(str3[1]));
        }

        alls = alls.stream().distinct().sorted().collect(Collectors.toList());
        add.forEach(pair -> a[find(pair.first)] += pair.second);
        for (int i = 1; i <= alls.size(); i++) {
            S[i] = S[i - 1] + a[i];
        }
        query.forEach(pair -> pw.println(S[find(pair.second)] - S[find(pair.first) - 1]));
        pw.close();
        br.close();
    }

    public static int find(int x) {
        int l = 0, r = alls.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (alls.get(mid) >= x) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        return l + 1;
    }
}