算法 离散化

整数离散化

适用条件

  1. 适用于有序的整数序列
  2. 该序列的值域很大,该序列的数的个数很少
  3. 使用的是数的相对大小而非绝对大小

算法思路

原数组 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开始映射)

原理

  1. 将数据从数组a中复制到b数组,对b排序
  2. 给b去重
  3. 将b的下标作为象征,将a数组每个元素使用二分查找在b中找到,并用b的下标值替换a数组的元素值。

这样就完成了离散化操作

存在的问题

  1. a 数组中可能存在重复的元素 去重
  2. 如何算出 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;
}

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

何hyy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值