离散化简介

离散化​


什么是离散化

定义

把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。

通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:

原数据:1, 999, 100000, 15;处理后:1, 3, 4, 2;

原数据:{100, 200},{20, 50000},{1, 400};

处理后:{3, 4},{2, 6},{1, 5};

特征

主要分为连续特征和离散特征。

离散化简介

离散化是程序设计中一个常用的技巧,它可以有效的降低时间复杂度。其基本思想就是在众多可能的情况中,只考虑需要用的值。离散化可以改进一个低效的算法,甚至实现根本不可能实现的算法。要掌握这个思想,必须从大量的题目中理解此方法的特点。例如,在建造线段树空间不够的情况下,可以考虑离散化。

数据的离散化

有些数据本身很大, 自身无法作为数组的下标保存对应的属性。如果这时只是需要这堆数据的相对属性, 那么可以对其进行离散化处理。当数据只与它们之间的相对大小有关,而与具体是多少无关时,可以进行离散化。

用来离散化的可以是大整数、浮点数、字符串等等。

离散化的实现

在 C++ 里面可以使用现成的 STL 算法来实现离散化。

将一个数组离散化,并进行查询是比较常用的情况:

其中 unique() 的返回值是一个迭代器,表示去重后容器中不重复序列的最后一个元素的下一个元素。
lower_bound(x) 返回大于等于 x 的第一个元素。

int a[N]; //a[i]表示初始数组,下标范围为[1, n]
int b[N]; //b[i]为a[i]的副本数组

sort(a + 1, a + 1 + n); //先排序

//len为离散化后数组的有效长度
//离散化整个数组的同时求出离散化后本质不同数的个数
int len = unique(a + 1, a + n + 1) - a - 1;

//离散后使用lower_bound()函数查询离散化之后的排名
for (int i = 1; i <= n; i++)
	b[i] = lower_bound(a + 1, a + len + 1, x) - a;

数组下标从 0 开始也是可以的:

int a[N], b[N], c[N]; //a[i]为原数组,b[i]为副本数组,c[i]为离散数组

memcpy(b, a, sizeof(a)); //拷贝
sort(a, a + n); //排序
int len = unique(a, a + n) - a; //去重
for (int i = 0; i < n; i++) 
    c[i] = lower_bound(a, a + len, a[i]) - a + 1; //查找

同样的,也可以对 vector 进行离散化:

vector<int> a, b; // b 是 a 的一个副本
sort(a.begin(), a.end());
a.erase(unique(a.begin(), a.end()), a.end());
for (int i = 0; i < n; ++i)
  b[i] = lower_bound(a.begin(), a.end(), b[i]) - a.begin();

实际应用

代码模板:

  1. 值域很大,但存有值的个数很少

  2. 总共的范围很大,但很多都是空的,用到的下标很少

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

//二分求出x对应的离散化的值
int find(int x){
    int l = 0, r = alls.size() - 1;
    while (l < r){
        int mid = l + r >> 1;
        if (alls[mid] >= x) //找到第一个大于等于x的位置
            r = mid;
        else 
            l = mid + 1;
    }
    return r + 1;//加1从1开始映射,不加1从0开始映射
}

例题:

原题链接:802. 区间和 - AcWing题库

【原题描述】

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。

现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。

接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l, r] 之间的所有数的和。

【输入格式】

第一行包含两个整数 n 和 m。(1 <= n, m <= 105)

接下来 n 行,每行包含两个整数 x 和 c。(-109 <= x <= 109 , -10000 <= c <= 10000)

再接下来 m 行,每行包含两个整数 l 和 r。(-109 <= l <= r <= 109)

【输出格式】

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

【输入样例】

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

【输出样例】

8
0
5

代码如下:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 3e5 + 5;

typedef pair<int, int> PII;

vector <int> alls;//存储所有离散化的值
vector <PII> add, query;//一个加,一个询问

int n;//n次存
int m;//m次询问
int a[N];//一个离散化之后的原数组
int s[N];//一个前缀和数组

//二分查找求出x对应的离散化的值
int find(int x){
    int l = 0, r = alls.size() - 1;
    while (l < r){
        int mid = l + r >> 1;
        if (alls[mid] >= x)//找到第一个大于等于x的值
            r = mid;
        else 
            l = mid + 1;
    }
    return r + 1;//加一是为了从1开始映射,如果不加一那就从0开始映射
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++){
        int x, c;
        cin >> x >> c;
        
        add.push_back({x, c});//每次操作将位置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()把原序列分成两段,把所有重复的元素放在末尾,不重复的元素按原来的相对顺序排在前面,
    //返回重复元素的第一个值的位置.
    //erase()接受两个参数,代表左右两个端点,删掉其中的元素
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    
    //处理插入
    //自动定义一个变量 item 属于add类型,并一次读取add中的数据
    for (auto item : add){
        int x = find(item.first);//x从1开始,first为值
        a[x] += item.second;//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);
        int r = find(item.second);
        
        cout << s[r] - s[l - 1] << endl;
    }
    
    return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BraumAce

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

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

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

打赏作者

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

抵扣说明:

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

余额充值