八、基础算法——离散化算法

文章介绍了离散化算法的基本思路,包括应用条件、举例和算法原理,特别强调了处理重复元素和计算离散化值的方法。提供了Java和C语言的模板实现,涉及排序、去重、二分查找等技术,并通过前缀和计算区间和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、基本思路

1、应用条件

  • 值域很大,如 0 ~ 10^9
  • 个数很少,如 0 ~ 10^5

2、举例

> 这里是引用

  • 注意此处是进行 index 的离散化,并且还需要进行一个离散化后的数组存储

3、算法原理

(1)a[ ] 可能存在重复元素

  • 去重

(2)如何计算原序列 index 离散化之后的值

  • 1. 对原数组插入索引(包括需要查询的索引)进行排序

  • 2. 进行索引判重 unique,返回非重复元素的离散数组的索引

  • 3. 根据返回索引,删除重复元素

  • 4. 根据二分法求解原序列索引 index 对应的离散化值(找到从左向右第一个大于等于index的离散化值对应的新索引)

  • 5. 对需要查询的值也进行二分查找离散化值

二、Java、C语言模板实现

//Java 模板实现
//假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n次操作,每次操作将某一位
//置 x上的数加 c。接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间
//的所有数的和。

import java.util.*;

public class Main {

    public static int unique(List<Integer> list){               // java版本去重函数,前提是已经进行了排序操作
        int j = 0;                                              // 此处是为了记录最后的allIndex函数去重后的长度,后面部分将会被删除
        for (int i = 0; i < list.size(); i++) {
            if (i == 0 || list.get(i) != list.get(i - 1)){      // 首先第一个肯定不同,后面的只要和前面的发现不相等,就可以确定为不是重复元素
                list.set(j, list.get(i));
                j++;
            }
        }

        return j;
    }

    public static int find(int index, List<Integer> list){      // 离散化函数,此函数是进行离散化映射的关键函数,将原来序列中的index,离散化后返回在allIndex中对应的索引
        int left = 0;
        int right = list.size() - 1;                            // 此处是左右边界信息的定义

        while (left < right){                                   // 二分法求解,用的是右边的序列
            int mid = left + right >> 1;
            if (list.get(mid) >= index){
                right = mid;
            }else {
                left = mid + 1;
            }
        }

        return left + 1;                                        // 此处之所以返回的是 +1, 是因为后面需要进行前缀和的求解,为了代码方便所以这样返回,根据二分法可知,最终left = right
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();                      // n 次操作进行相加
        int m = scanner.nextInt();                      // m 次问询操作,也就是前缀和查找次数

        int N = 3000010;                                // 此处应该是 n + 2m,n 是因为需要插入n次,2*m 是因为每次问询都会插入 left 和 right 两个坐标
        int[] a = new int[N];                           // 存储映射完之后对应的 allIndex 的索引对应的序列值
        int[] s = new int[N];                           // 存储映射之后的 a 的前缀和

        List<Integer> allIndex = new ArrayList<>();     // 最重要的操作列表,在里面存储的是加入值的坐标,而其本身的索引是离散化之后的索引
        List<Pair> add = new ArrayList<>();             // Pair 存储,第一个存的是加入的原序列索引,第二个是 value
        List<Pair> query = new ArrayList<>();           // Pair 存储,第一个存的是问询的原序列索引 left,第二个是left

        for (int i = 0; i < n; i++) {
            int x = scanner.nextInt();
            int c = scanner.nextInt();

            add.add(new Pair(x, c));
            allIndex.add(x);                            // 在此处加入一部分需要离散化的坐标索引
        }

        for (int i = 0; i < m; i++) {
            int left = scanner.nextInt();
            int right = scanner.nextInt();

            query.add(new Pair(left, right));
            allIndex.add(left);                         // 之所以 left,right 都需要加入是因为我们后面需要用问询的映射来进行访问已经离散化的坐标,所以我们需要知道他在离散化之后在哪里才能对应
            allIndex.add(right);
        }

        // 离散化重要步骤:排序 + 去重 + find离散化
        Collections.sort(allIndex);                     // 排序操作
        int end = unique(allIndex);                     // 进行去重操作,返回的是最后的索引
        allIndex = allIndex.subList(0, end);            // 去掉末尾

        for (Pair item : add) {
            int index = find(item.first, allIndex);
            a[index] += item.second;                    // 注意此处为 += 才对,因为可能对于同一个位置操作多次
        }

        for (int i = 1; i <= allIndex.size(); i++) {    // 前缀和公式
            s[i] = s[i - 1] + a[i];
        }

        for (Pair item : query) {
            int left = find(item.first, allIndex);      // 问询离散化之后的索引所在,这也就是前面为什么还要把索引加入allIndex
            int right = find(item.second, allIndex);
            System.out.println(s[right] - s[left - 1]);
        }
    }

}

class Pair{
    int first;
    int second;

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

```c
// C语言实现,此处是yxc实现
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
}

三、注意事项

  • 主要算法步骤:排序 + 去重 + 二分索引离散化
  • 其余细节问题在代码注释中已经体现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牙否

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值