算法 离散化
整数离散化
适用条件
- 适用于有序的整数序列
- 该序列的值域很大,该序列的数的个数很少
- 使用的是数的相对大小而非绝对大小
算法思路
原数组 a :
数组下标:0 1 2 3 4
数组元素:1 2 2 5 109
映射数组 :
数组下标:0 1 2 3
数组元素:0 1 2 3(从0开始映射)
1 2 3 4(从1开始映射)
原理
- 将数据从数组a中复制到b数组,对b排序
- 给b去重
- 将b的下标作为象征,将a数组每个元素使用二分查找在b中找到,并用b的下标值替换a数组的元素值。
这样就完成了离散化操作
存在的问题
- a 数组中可能存在重复的元素 去重
- 如何算出 a 数组中每个元素离散化后的值 二分
其实就是找出在 a 数组中的下标是多少
模板
C
#include<studio.h>
int main()
{
int a[100]={0};
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
for(int j=0;j<n-1;j++){
for(int k=0;k<n-j-1;k++){
if(a[k]>a[k+1]){
int t=a[k];
a[k]=a[k+1];
a[k+1]=t;
}
}
}
int i=0,j=1;
int len=n;
while(i>=len-1 && j>=len-1){
while(a[i]!=a[j] && j<len){
i=j;
j++;
}
while
}
C++
需去重
vector<int> alls;
//存储所有待离散化的值
sort(alls.begin(),alls.end());
//将数组中所有值排序
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//去掉重复元素
//unique()函数将数组中所有元素去重,并且返回去重之后数组的尾端点的下标,erase()函数将返回的尾端点和数组末尾之间的数据去掉
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;
//返回下标,r+1 表示从1开始映射,r 表示从0开始映射
}
具体参考下面区间和例题
无需去重
一边插入一边映射,类似于用二分优化插入排序。
使用插入排序方法,将数轴上的位置的相对大小作为判断依据进行排序,在每次接收时对数据进行同步处理,从第二个接收开始,先查看在该数组中,是否对此位置进行过操作,用二分查找进行判断,如果有,直接在对应位置上的数值进行增加,如果没有,使用插入排序中的插入操作把该数据直接插入数组中,这样的话,对于整个数组就不会有重复位置,即在数轴上没有相同的位置,因为会先进行查找,如果发现对某个位置已经进行过操作时,直接将其数值增加。所以在整个数组中没有一个相同的在数轴上的位置,并且是按照相对大小进行排序的,始终是有序的。
如何实现unique()函数
返回值
返回的是一个vector<int>的迭代器
基本实现思路:双指针算法
什么样的元素是不同的:排好序之后,1.它是第一个元素 2.a[i]!=a[i-1]
代码
vector<int>::iterator unique(vector<int> &a)
{
int j=0;
//存下当前存到第几个数,j <= i
for(int i=0;i<a.size();i++)
//遍历所有的数
if(!i || a[i]!=a[i-1])
a[j++]=a[i];
//a[0]~a[j-1]就是所有a中不同的数
return a.begin()+j;
}
例题:区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是0。
现在,我们首先进行 n 次操作,每次操作将某一位置x上的数加c。
接下来,进行 m 次询问,每个询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和。
输入格式
第一行包含两个整数n和m。
接下来 n 行,每行包含两个整数x和c。
再接下里 m 行,每行包含两个整数l和r。
输出格式
共m行,每行输出一个询问中所求的区间内数字和。
数据范围
−10e9≤x≤10e9,
1≤n,m≤10e5,
−10e9≤l≤r≤10e9,
−10000≤c≤10000
输入样例
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例
8
0
5
当数组下标比较小的时候(数据范围比较小),可以用前缀和来完成
当数据范围比较大的时候,用离散化来完成
代码
//读入所有操作,将用到的位置进行离散化
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII; //PII将两个数绑定在一块,一个作为first,一个作为second
const int N = 300010;//插入操作是十万(n),查询操作是二十万(2m)
int n, m;
int a[N], s[N]; //a是用到的数组成的数组,s是前缀和数组
vector<int> alls; //说明里面存储的是int类型,相当于一个int类型的数组,不用考虑数组长度,alls存的是所有用到的下标
vector<PII> add, query;//插入操作,是pair<int,int>类型的数组,add和query是名称
int find(int x) //求x的值离散化之后的结果,求所用到位置(x)离散化之后的值
//其实就是对于排序、去重之后的alls数组(所有用到的位置),返回x的下标(从0算起)/下标+1(从1算起)
{
int l = 0, r = alls.size() - 1; //size是返回vector中有多少个有效数据
while (l < r)
{
int mid = (l + r) >> 1;
if (alls[mid] >= x)
r = mid;
else
l = mid + 1;
}
return r + 1;//映射到从1开始的自然数
}
int main()
{
cin >> n >> m;
for (int i = 0;i < n;i++)
{
int x, c;
cin >> x >> c;
add.push_back({ x,c }); //向vector末尾加数据,调用pair类型
alls.push_back(x); //所有的点放在alls中,包括数字的点和区间的点
}
for (int i = 0;i < m;i++)
{
int l, r;
cin >> l >> r;
query.push_back({ l, r }); //每次询问的左端点和右端点放在query中
alls.push_back(l);
alls.push_back(r);
}
//去重
sort(alls.begin(), alls.end());//排序,指向数组数组首元素的指针,指向数组末尾元素后一位指针,第三个是一个函数指针,也可以是函数对象【在类中对()进行重载】,仿函数,类似于函数,第三个参数用来制定排序规则
alls.erase(unique(alls.begin(), alls.end()), alls.end());//unique()把数组中所有重复元素删去,把不重复的元素放到数组的前面,
//返回的是新数组的一个最后位置,把新数组最后一个位置到原来数组末尾的数据删除,即去重
//处理插入
//erase()用来删除
//处理插入
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;
}