离散化
什么是离散化
定义:
把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:
原数据: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();
实际应用
代码模板:
-
值域很大,但存有值的个数很少
-
总共的范围很大,但很多都是空的,用到的下标很少
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;
}