【基础算法系列】离散化与前缀和算法的运用

news2024/10/7 20:35:23

⭐️前面的话⭐️

本篇文章将主要介绍离散化算法,所谓离散化算法,就是将一个无限区间上散点的数,在不改变相对大小的情况下,映射到一个较小的区间当中,然后对这个较小的区间进行操作的过程就是离散化的过程,我将会以一道经典的离散化问题为栗子,介绍离散化(顺便介绍前缀和)算法,展示代码:Java/C++。

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴2022年11月20日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:无
💬参考在线编程网站:🌐牛客网🌐力扣🌐acwing
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


📌导航小助手📌

    • 🍒1.算法简介
    • 🍒2.离散化实战:区间和
      • 🍓题目详情
      • 🍓解题思路
      • 🍓代码实现


封面区


🍒1.算法简介

离散化算法,所谓离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。

就比如在一个很大的区间里面,有有限个数,比如[2333, 4888, 9888, 1212342313, 139, 10390, 8999932]这几个数,离散化就是将这些映射到另外一个区间中,在不改变相对大小的情况下,从小到大依次去重排序,得到序列[139, 2333, 4888, 9888, 10390, 8999932, 1212342313],将这些数按照从小到大的顺序存入到数组当中,相对顺序不发生改变,但形成了与数组下标的映射,即139->0, 2333->1, 4888->2, 9888->3…这些数都按照大小顺序形成了映射,通过映射的下标,我们可以找到原来的那个数,如0就对应着139,也可以通过原数据通过二分的方法找到下标。

除了手动使用数组使元素与下标形式映射,还可以使用哈希表来进行建立映射关系。

1

离散化算法,常常会与其他算法结合起来使用,比如前缀和算法,差分等算法结合使用,一般使用在一个很大甚至无限大的区间中,但是需要查询或操作的数是有限的情形当中,遇到这种情况我们一般会使用离散化进行处理。

下面我们来看一道题目,来加深对离散化的理解。

🍒2.离散化实战:区间和

🍓题目详情

题目链接:802. 区间和

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

输入格式:
第一行包含两个整数 n n n m m m
接下来 n n n 行,每行包含两个整数 x x x c c c
再接下来 m m m 行,每行包含两个整数 l l l r r r

输出格式:
m m m 行,每行输出一个询问中所求的区间内数字和。

数据范围
− 1 0 9 ≤ x ≤ 1 0 9 −10^9≤x≤10^9 109x109,
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105,
− 1 0 9 ≤ l ≤ r ≤ 1 0 9 −10^9≤l≤r≤10^9 109lr109,
− 10000 ≤ c ≤ 10000 −10000≤c≤10000 10000c10000
输入样例:

3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例:

8
0
5

🍓解题思路

题目的意思非常简单,就是先对一个很大的区间(初始值均为0)进行n次的修改操作(就是随机对区间内的某一个坐标进行加上一个数c的操作),修改操作结束后再进行m次的查询操作,每次需要返回区间 [ l , r ] [l,r] [l,r]上所有值的和。

根据题目所给的限制条件 1 < = n , m < 1 0 5 1<=n,m<10^5 1<=n,m<105,不难计算出总操作次数不会超过 N = 3 × 1 0 5 N=3 \times 10^5 N=3×105次,也就是说,最多只会有 N N N坐标位置的数被修改或者查询,所以我们可以考虑使用离散化进行处理,我们可以申请一个大小为 N N N的数组,按照大小顺序存储所有进行过操作(包括修改和查询)的位置,这样就将原来大小很大的区间压缩到了一个比较小的区间,再建一个大小为 N N N的数组用来储存这些位置上的值,在这个数组上进行修改和查询操作。

由于我们还需要进行区间和的查询,因此还需要一个 N + 1 N+1 N+1大小的前缀和数组。

所以我们可以预先定义一下:

    public static final int N = (int) 3e5 + 233;
    //储存离散化后的数据
    public static int[] a = new int[N];
    //储存前缀和
    public static int[] s = new int[N];
    //储存原来排序去重可能访问的位置
    public static TreeSet<Integer> ts = new TreeSet<>();
    public static List<Integer> list;
    //储存修改的操作
    public static List<int[]> add = new ArrayList<>();
    public static List<int[]> query = new ArrayList<>();

c++也有哦:

const int N = (int) 3e5 + 233;
//离散化数组与前缀和数组
int a[N], s[N];
//使用pair进行储存
typedef pair<int, int> PAI;
//储存所有修改,查询的位置
vector<int> alls;
//分别储存修改和查询信息
vector<PAI> add, query;

那如何如果原位置找到离散化数组中的下标呢?很简单,因为数组是有序的,所以我们可以通过二分的方式找到对应的下标。

二分代码如下:
Java版本:

    //二分法找x位置在离散化数组中的下标
    private static int find(int x) {
        int l = 0;
        int r =ts.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (list.get(mid) >= x) r = mid;
            else l = mid + 1;
        }
        //返回下标,为了前缀和查询方便,我们返回下标+1
        return l + 1;
    }

c++版本:

//使用二分实现查找离散化数组的下标
int find(int x) 
{
    int l = 0;
    int r = alls.size() - 1;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        //寻找第一个大于或等于x在alls中的下标
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    //返回下标,为了前缀和查询方便,我们返回下标+1
    return l + 1;
}

2
对于区间查询,我们可以结合前缀和来进行实现,我们可以直接对离散化所映射储存数据的那个数组直接求 n n n次修改操作后的前缀和,因为原来区间内所有的初始值均为 0 0 0,所有经过 n n n次的单点修改后,对离散化所储存的数据求前缀和和对原来区间求前缀和得到的结果是一样的。

顺便在这里讲一下前缀和吧,所谓前缀和,就是将区间内的前 i i i个数加起来的数就是前缀和,对数组所有位置求的前缀和组成的数组叫做前缀和数组。

3
不妨设原数组为 a r r arr arr, 前缀和数组为 s u m sum sum,则根据前缀和 i i i下标储存的是原数组 a r r arr arr i i i个元素的和可以知道:
s u m [ i ] = s u m [ i − 1 ] + a r r [ i − 1 ] , 其 中 i > 0 sum[i]=sum[i-1]+arr[i-1],其中i>0 sum[i]=sum[i1]+arr[i1]i>0

前缀和最大的优势就是可以直接得到某一个区间的和。

4

下面我们来写代码,通过代码来实现我们的逻辑。

🍓代码实现

java版本:

import java.util.*;

class Main {
    public static final int N = (int) 3e5 + 233;
    //储存离散化后的数据
    public static int[] a = new int[N];
    //储存前缀和
    public static int[] s = new int[N];
    //储存原来排序去重可能访问的位置
    public static TreeSet<Integer> ts = new TreeSet<>();
    public static List<Integer> list;
    //储存修改的操作
    public static List<int[]> add = new ArrayList<>();
    public static List<int[]> query = new ArrayList<>();
    
    //二分法找x位置在离散化数组中的下标
    private static int find(int x) {
        int l = 0;
        int r =ts.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (list.get(mid) >= x) r = mid;
            else l = mid + 1;
        }
        return l + 1;
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        int n = sc.nextInt();
        int m = sc.nextInt();
        
    //输入修改数据
    for (int i = 0; i < n; ++i) {
        int x = sc.nextInt();
        int c = sc.nextInt();
        ts.add(x);
        
        add.add(new int[]{x, c});
    }
    //输入查询数据
    for (int i = 0; i < m; ++i) {
        int l = sc.nextInt();
        int r = sc.nextInt();
        
        ts.add(l);
        ts.add(r);
        
        query.add(new int[]{l, r});
    }
    
    //修改离散化的下标的数据
    //因为set不支持随机访问,我们使用它来构造一个list
    list = new ArrayList<>(ts);
    //遍历修改操作,进行单点修改
    for (int[] item : add) {
        int x = find(item[0]);
        int c = item[1];
        
        a[x] += c;
    }
    
    //求前缀和
    for (int i = 1; i <= ts.size(); ++i) {
        s[i] = s[i - 1] + a[i];
    }
    
    //输出离散化后区间的和
    for (int[] item : query) {
        int l = find(item[0]);
        int r = find(item[1]);
        
        int ans = s[r] - s[l - 1]; 
        System.out.println(ans);
    }
    
    }
}

C++代码实现:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = (int) 3e5 + 233;
//离散化数组与前缀和数组
int a[N], s[N];
//使用pair进行储存
typedef pair<int, int> PAI;
//储存所有修改,查询的位置
vector<int> alls;
//分别储存修改和查询信息
vector<PAI> add, query;

//使用二分实现查找离散化数组的下标
int find(int x) 
{
    int l = 0;
    int r = alls.size() - 1;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        //寻找第一个大于或等于x在alls中的下标
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    //返回下标,为了前缀和查询方便,我们返回下标+1
    return l + 1;
}

int n, m;
int main()
{
    //读入数据
    cin >> n >> m;
    for (int i = 0; i < n; ++i)
    {
        int x, c;
        cin >> x >> c;
        alls.push_back(x);
        add.push_back({x, c});
    }
    //读入查询数据
    for (int i = 0; i < m; ++i)
    {
        int l, r;
        cin >> l >> r;
        alls.push_back(l);
        alls.push_back(r);
        
        query.push_back({l, r});
    }
    //离散化,1.排序 2.去重 Java中就是treeset
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    
    //映射+二分查找+修改
    for (PAI item : add)
    {
        int x = find(item.first);
        a[x] += item.second;
        //cout << a[x] << endl;
    }
    //求前缀和
    for (int i = 1; i <= alls.size(); ++i)
    {
        //注意在这里 离散化数据数组从1位置开始存数据 所以s[i]=s[i-1]+a[i]
        s[i] = s[i - 1] + a[i];
        //cout << s[i] << endl;
    }
    
    //输出区间和
    for (PAI item : query)
    {
        int l = find(item.first);
        int r = find(item.second);
        
        cout << s[r] - s[l - 1] << endl;
    }
    
    return 0;
}

相关练习题:
待补充

觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦!

1-99

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/19801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++笔试强训】第二十八天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

微信小程序自定义tabBar(实操)

文章目录一、前言二、固定效果图实现步骤实现步骤完整代码-矢量图images图片app.json代码三、自定义效果图实现步骤实现步骤完整代码-矢量图images图片app.json代码custom-tab-bar下的代码使用自定义TaBar一、前言 一般使用tabBar的样式&#xff0c;固定不能改变。如下固定效果…

java计算机毕业设计springboot+vue村委会管理系统

项目介绍 本村委会管理系统是针对目前村委会管理的实际需求,从实际工作出发,对过去的村委会管理系统存在的问题进行分析,完善用户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高,节省了开支,提高了工作的效率。 本系统结合计算机系统的结构、概…

DPDK-A3: KVM使用SRIOV和虚机使用DPDK

虚拟机基本管理 如下命令可以修改默认网段 sudo virsh net-edit --network default<network><name>default</name><uuid>45ed012c-3933-4f3e-9575-b37bffa21b83</uuid><forward modenat/><bridge namevirbr0 stpon delay0/><ma…

解决javax.xml.parsers.DocumentBuilderFactory.setFeature(Ljava/lang/String;Z)V异常

文章目录异常&#xff1a;不同jar包的多xml解析器冲突解决其他异常&#xff1a; java.lang.AbstractMethodError:javax.xml.parsers.DocumentBuilderFactory.setFeature(Ljava/lang/String;Z)可能原因&#xff1a; 在本地WINDOWS编译测试没问题&#xff0c;只在LINUX服务器上面…

通讯录的实现【涉及动态内存管理和文件操作】【从易到难】【详解】

本期介绍&#x1f356; 主要介绍&#xff1a;如何实现一个通讯录&#xff0c;从静态版通讯录&#xff0c;到动态内存版通讯录&#xff0c;再到文件存储版通讯录&#xff0c;详细讲述了每一个通讯录的实现步骤以及思维逻辑&#xff0c;以及通讯录的完整代码&#x1f440;。 文章…

基于Springboot+mybatis+mysql+html图书管理系统

基于Springbootmybatismysqlhtml图书管理系统一、系统介绍二、功能展示1.用户登陆2.图书管理3.读者管理4.借还管理5.密码修改6.图书查询&#xff08;读者&#xff09;7.个人信息&#xff08;读者&#xff09;8.我的借还&#xff08;读者&#xff09;一、系统介绍 系统主要功能…

深究为啥Vue管理的函数不能是箭头函数

首先明确一点&#xff0c;箭头函数的this指向是根据上下文作用域确定的 Vue框架中&#xff0c;容易搞错的一点就是认为对象也有作用域 了解作用域与作用域链这个问题就迎刃而解了 假设Vue管理的函数是箭头函数时&#xff1a; 此时this是windows&#xff0c;Vue中data与metho…

生物识别技术在汽车领域带来了巨大变革

智能汽车时代 2022年10月28日&#xff0c;工信部发布《道路机动车辆生产准入许可管理条例&#xff08;征求意见稿&#xff09;》&#xff08;“《准入管理条例草案》”&#xff09;。包含了更全面的汽车准入管理规定&#xff0c;同时较为系统地增加了针对智能汽车的准入管理规定…

更简单的读取和存储对象

在上一篇文章中我们已经介绍在XML文件注册Bean的具体步骤,这一篇文章将会介绍使用更加简洁的方式(使用注解)来存储和读取Bean.这也是最常用的方法. 1. 创建并配置好Spring项目 和上一篇的步骤相同,下面就相当于复习如何创建Spring项目吧 创建一个 Maven 项目为 Spring 项目…

微信小程序 | 酷炫时钟样式整理【附源码】

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …

对给定的数组进行重新排列numpy.random.shuffle()和numpy.random.permutation()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 对给定的数组进行重新排列 numpy.random.shuffle()和 numpy.random.permutation() [太阳]选择题 请问对以下Python代码说法错误的是&#xff1f; import numpy as np anp.arange(6) print(【…

零基础带你基于vue2架构搭建qiankun父子项目微前端架构

这里建议大家用 14版本左右的node版本 我们先创建一个目录 就叫qiankun 然后在终端打开 qiankun 目录 在终端输入指令 vue create vue-qiankun-base创建一个叫 vue-qiankun-base的vue项目 版本大家先选择vue2 vue-qiankun-base项目将作为我们的基座 然后在终端输入 vue …

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.3 手机验证码案例 - 生成验证码

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.3 手机验证码案例 - 生成验证码5.3.1 SpringBoot …

众焱公司网络平台建设-传输网的规划与设计

目 录 摘 要 I Abstract II 第一章 项目概述 1 1.1 项目目标 1 1.1.1 总体目标 1 1.1.2 阶段目标 1 1.2 设计原则 2 1.3总体拓扑图设计 3 第二章 应用分析 4 2.1 应用分类 4 2.1.1 应用系统总体框架 4 2.1.2 业务系统应用分类 5 2.1.3 信息管理系统应用分类 6 2.2 数据中心及分…

数据结构:栈和队列

栈是一种特殊的线性结构&#xff0c;只允许在栈顶进行进行插入和删除操作。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出&#xff08;先进后出&#xff09;LIFO&#xff08;Last In First Out&#xff09;的原则。 类比成将子…

小学生python游戏编程arcade----爆炸粒子类

小学生python游戏编程arcade----爆炸粒子类前言1.1 参数设置粒子加速下降的速度。如果不需要&#xff0c;则为0粒子退出的速度粒子移动的速度。范围为2.5<-->5&#xff0c;设置为2.5和2.5。每次爆炸有多少粒子粒子直径多大粒子颜色列表我们有可能将纹理翻转为白色&#x…

芒果改进YOLOv7系列:首发改进特征融合网络BiFPN结构,融合更多有效特征

💡统一使用 YOLOv7 代码框架,结合不同模块来构建不同的YOLO目标检测模型。文章目录 一、BiFPN论文理论部分代码部分YOLOv7+BiFPN在这篇文章中,将BiFPN结构加入到 YOLOv7 结构中 一、BiFPN论文理论部分 EfficientDet: Scalable and Efficient Object Detection BiFPN与P…

芯天下在创业板过会:预计全年收入将达到10亿元,净利润约2亿元

11月18日&#xff0c;深圳证券交易所创业板披露的信息显示&#xff0c;芯天下技术股份有限公司&#xff08;下称“芯天下”&#xff09;获得上市委会议通过&#xff0c;即IPO过会。据贝多财经了解&#xff0c;芯天下于2022年4月28日在创业板递交上市申请材料。 本次冲刺创业板上…

vins-mono初始化代码分析

大体流程 初始化主要分成2部分&#xff0c;第一部分是纯视觉SfM优化滑窗内的位姿&#xff0c;然后在融合IMU信息。 这部分代码在estimator::processImage()最后面。 主函数入口&#xff1a; void Estimator::processImage(const map<int, vector<pair<int, Eigen:…