C++ 离散化 算法 (详解)+ 例题

1、性质

  • 无限空间 中有限的个体映射到有限的空间中去,以此提高算法的空间效率。通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的压缩。
  • 适用范围:数的跨度很大,用的数很稀疏

  • 例如:值域:1~10^9, 个数:10^5,值域很大,但是用到个数相对很少,这个时候就可以离散化

    • 比如:将

      cpp 复制代码
      a[i] : 1 3 100 2000 50000//这里需要注意可以离散化的前提是数组元素必须是有序的
        i  : 0 1  2    3    4  //上面数字映射(离散化)后对应的下标

2、实现方式

  • 我觉得离散化(映射)最大的难点是前后映射关系 ,如何能够将不连续的点映射到连续的数组的下标 。此处的解决办法就是开辟额外的数组alls[] )存放原来的数组下标(被离散化的数的下标)。

  • 手动模拟如下:

    cpp 复制代码
    对于一组数如 :[1,3,2000,50000,-99,2000,3,30],首先排序去重后----->[-99,1,3,30,2000,50000]
    从排序去重后数组可以得到关系:-99 --> 0, 1 --> 1 , 3 --> 2, 30 -->3 ..........
    上面对应的0,1,2,3,......都是此数字映射后对应的相对下标
    • 离散化基本步骤可分为:

      1、离散化一定是有序的去离散化(排序)

      2、alls[]中可能存在重复元素**(所以 去重)**

      3、 如何算出X求映射的下标 )离散化后的值 (二分查找)

      注意:对于上述第三条,可以理解为去找这个数映射后的数组下标。

  • 代码模板:

    cpp 复制代码
    vector<int> alls; //存储所有待离散化的值
    sort(alls.begin(),alls.begin());//将所有值排序
    alls.erase(unique(alls.begin(),alls.end()),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映射到(1,2,3,4.......n),不加1的话映射到(0,1,2,3.....n)
    }
  • 关于代码中的unique函数和erase函数:

如何自己实现unique函数:

cpp 复制代码
例如:[1,1,2,2,2,3,4,5,5,5,6];
分为两步:
1、首先它是第一个元素
2、a[i] != a[i+1]

unique函数实现代码(双指针算法):

cpp 复制代码
vector<int> ::interator unique(vector<int> &a)
{
    for(int i=0;j=0;i<a.size();i++)
    {
        if(!i || a[i] != a[i-1])
        {
            a[j++] = a[i];
        }
    }
    //a[0]~a[j-1]中存的所有a[i]中不重复的数
    rerturn a.begin()+j;
}

3、例题:802. 区间和 - AcWing题库

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

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含两个整数 x 和 c。

再接下来 m 行,每行包含两个整数 l 和 r。

输出格式

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

数据范围
−10^9≤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

  • 图解+分析(来源于大佬的题解分析):

复制代码

AC代码:

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
#include<utility>

using namespace std;

typedef pair<int, int> PII;

//这里开30W是因为n个x操作和2*m个坐标
const int N = 3e5+10;
int a[N],s[N];//前缀和数组
int n,m;
vector<int> alls;//离散化数组(对应下标值)
vector<PII> add,query;

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;
    }
    //如果+1映射从1,2,3........
    //否则从0,1,2,3、、、、、、、
    return r + 1;//返回映射
}

int main()
{
    cin >> n >> m;
    for(int i=0;i<n;i++)
    {
        int x,c;
        cin >> x >> c;
        //添加该位置加c的
        add.push_back({x,c});
        //把坐标放进离散化数组
        alls.push_back(x);
    }
    
    for(int i=0;i<m;i++)
    {
        int l,r;
        cin >> l >> r;
        //插入访问坐标
        query.push_back({l,r});
        //放入离散化数组里
        alls.push_back(l);
        alls.push_back(r);
    }
    
    //离散化板子
    sort(alls.begin(),alls.end());//排序
    //unique:返回去重后最后一个不重复元素的位置
    alls.erase(unique(alls.begin(),alls.end()),alls.end());//去重
    for(auto item : add)
    {
        int x = find(item.first);
        a[x] += item.second;
    }
    
    //处理前缀和数组
    for(int i=1;i<=alls.size();i++) s[i] = s[i-1] + a[i];
    
    //处理询问
    for(auto item : query)
    {
        int l = find(item.first),r = find(item.second);
        cout << s[r] - s[l-1] << endl;
    }
    return 0;
}

代码+样例模拟:

相关推荐
想跑步的小弱鸡4 小时前
Leetcode hot 100(day 3)
算法·leetcode·职场和发展
xyliiiiiL6 小时前
ZGC初步了解
java·jvm·算法
爱的叹息6 小时前
RedisTemplate 的 6 个可配置序列化器属性对比
算法·哈希算法
Merokes7 小时前
关于Gstreamer+MPP硬件加速推流问题:视频输入video0被占用
c++·音视频·rk3588
独好紫罗兰7 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
每次的天空7 小时前
Android学习总结之算法篇四(字符串)
android·学习·算法
请来次降维打击!!!8 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
qystca8 小时前
蓝桥云客 刷题统计
算法·模拟
别NULL8 小时前
机试题——统计最少媒体包发送源个数
c++·算法·媒体
嘤国大力士9 小时前
C++11&QT复习 (七)
java·c++·qt