7.离散化

主要思想:

有的数组,其元素的值域很大,比如数组中的元素范围很大,下标范围为[-10^9, 10^9],但元素的个数很少,比如只有1000个元素。

有时(例如计数排序的思想),我们需要将元素的值,作为数组的下标来操作。此时不可能开一个2*10^9大小的数组。此时我们把这些元素,映射到下标从0(或者从1)开始的数组中。(也可以理解为对稀疏数组进行压缩)

或者理解为较大的数组下标与较小的数组下标建立映射关系

例如:

有一个数组a,存储1, 3, 100, 2000, 500000,此时若需要用该数组中元素值作为数组下标进行某种操作,这就意味着操作数组下标范范围很大但实际存储数据的位置就那么几个,所以我们把这个数组中的元素,映射到与[0, 1, 2, 3, 4]自然数相对应,即用一个新数组all[],令all[0]=1,all[1]=3….all[4]=500000,这个映射的过程,称之为离散化实际上离散化的是数组下标值,而不是实际数据,因为要将这些元素值作为下标去操作一个数组

离散化要点:

  • 用数组all从下标0开始存储原数组a中的值,要求离散化数组all有序,且若有重复元素,可能需要去重(利用C++中的库函数实现排序和去重)
  • 实现离散化,即得到离散化值(find函数返回数组下标),就能代表形成映射关系,元素值与数组下标对应
  • 对离散化后的值进行操作:由于数组all已经排好序,故这里用二分查找x在数组all中的位置q(q:0~n-1,即实现离散化)

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<int> v;//离散化的数组
sort(v.begin(),v.end());//将数组排序 C++中的sort函数
v.erase(unique(v.begin(),v.end()),v.end());//对数组去重 C++中的unique、erase函数
//unique函数将数组v的全部重复元素删掉,再把不重复元素全部放到数组前面,返回新数组的最后一个位置,然后用erase函数将这个位置到最后一个位置的元素删掉

//查找数x在数组中的位置即下标 二分查找,实际上就是x对应的离散化值(映射到0/1开始的自然数)
int find(int x){
int l=0,r=v.size()-1;
while(l<r){
int mid=(l+r)/2;
if(v[mid]>=x) r=mid;
else l=mid+1;
}
return r+1;//l+1也可,这里是否加1和题目有关,若题目设计前缀和差分,要从1开始,则需要加1

}

Acwing 802.区间和

实现思路:

  • 先读入插入操作需要的数字(x,c)将其存储在add类型的数组当中,同时将x压入alls数组当中。再读入问询操作需要的数字(l,r)将其存储在query类型的数组当中,同时将l,r压入alls数组当中(注意这里想x,l,r都代表坐标轴位置)。
  • 再对数组alls进行排序,去重操作,运用c++自带的sort函数对alls数组进行排序,再使用erase函数和unit函数对其进行去重操作。
  • 再处理加数操作,利用二分查找,得到x在alls数组的位置q(注意加1,因为后续要算区间和即前缀和,数组从1开始),在创建一个数组a,a[q]就与坐标位置相对应(初始值为0),a[q]+c就表示对应坐标位置+c。
  • 再求a数组前缀和数组,即s[i]=s[i-1]+a[i],循环终止条件为i=alls.size()(因为二分查找最后一个位置加1)。
  • 再求区间和,同加数操作,利用二分查找,得到l,r在alls数组的位置,通过前缀和数组求出区间和(s[r]-s[l-1])。

其他理解

AcWing 802. 区间和 - AcWing

http://t.csdnimg.cn/XPtTa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef pair<int,int> PII;//用于存储对应数,如x和c,l和r

const int N=3e6+10;
vector<int> alls;//需离散化数组
int a[N],s[N];//数组a代表坐标轴上的数,进行加数操作,并求前缀和数组s
int n,m;
vector<PII> add,query;//x和c,l和r

//形成离散化值 得到x对应下标位置 注意加1,因为后续要求前缀和
int find(int x){
//二分查找x的位置
int l=0,r=alls.size()-1;
while(l<r){
int mid=(l+r)/2;
if(alls[mid]>=x) r=mid;
else l=mid+1;
}
return r+1;
}

int main(){
cin>>n>>m;
while(n--){
int x,c;
cin>>x>>c;
add.push_back({x,c});
alls.push_back(x);//同时将x加入需离散化数组
}
while(m--){
int l,r;
cin>>l>>r;
query.push_back({l,r});
//同时将l,r加入离散化数组
alls.push_back(l);
alls.push_back(r);
}
//排序
sort(alls.begin(),alls.end());
//去重
alls.erase(unique(alls.begin(),alls.end()),alls.end());

//取离散化值完成对应操作
for(auto item:add){
int q;
q=find(item.first);
a[q]+=item.second;//坐标轴x位置加c
}
//计算前缀和
for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];

//输出区间和
for(auto item:query){
int l,r;
l=find(item.first);
r=find(item.second);
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}