离散化

一、概述

数据离散化是一个非常重要的思想。

为什么要离散化?当以权值为下标的时候,有时候值太大,存不下。 所以把要离散化的每一个数组里面的数映射到另一个值小一点的数组里面去。

打个比方,某个题目告诉你有10^4个数,每个数大小不超过10^10,要你对这些数进行操作,那么肯定不能直接开10^10大小的数组,但是10^4的范围就完全没问题。

我们来看一下定义:离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。(by百度百科)

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

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

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

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

但是离散化仅适用于只关注元素之间的大小关系而不关注元素本身的值!

二、原理与操作

假如你只想简单操作一下,如求个逆序对什么的,那直接排序后将它的顺序覆盖回去就可以啦。(它不能去重)

假如你想写的更加专业就要采用以下步骤:

1、排序

2、去重

3、索引

首先我们要对所要进行离散化的数据进行排序:一般使用sort对数组或结构体排序。

然后是去重操作,为了写出高效的代码,我们需要复习两个STL函数:unique()和lower_bound(),他们同时隶属于#include<algorithm>。

unique的作用是“去掉”容器中相邻元素的重复元素(不一定要求数组有序),它会把重复的元素添加到容器末尾(所以数组大小并没有改变),而返回值是去重之后的尾地址;

函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置。

【ps.upper_bound是返回第一个大于b[x]的指针,upper_bound()=lower_bound()+1】

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int lsh[1000], lshcopy[1000], sy[1000]; //lsh[n]是即将被离散化的数组,lshcopy[n]是a[n]的副本,sy[n]用于排序去重后提供离散化后的值
int main()
{
	int n;	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&sy[i]);
		lshcopy[i]=sy[i];
			
	} 
	sort(sy,sy+n);//第一步排序 
    int size=unique(sy,sy+n)-sy;//unique显示去重后的个数 
    printf("size is : %d",size);
    printf("\n");
    for(int i=0;i<n;i++)
    {
    	lsh[i]=lower_bound(sy,sy+size,lshcopy[i])-sy; //即lsh[i]为lshcopy[i]离散化后对应的值  
		printf("lsh is : %d",lsh[i]);  	
	}
 
}

三、应用

题目

题目链接

AcWing的OJ,https://www.acwing.com/problem/content/804/

我的OJ,http://47.110.135.197/problem.php?id=5239

题目描述

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
近下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l, r] 之间的所有数的和。

输入

第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下里 m 行,每行包含两个整数 l 和 r。

输出

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

样例输入

 
  1. 3 3

  2. 1 2

  3. 3 6

  4. 7 5

  5. 1 3

  6. 4 6

  7. 7 8

样例输出

 
  1. 8

  2. 0

  3. 5

数据范围

−10^9 ≤ x ≤ 10^9,
1 ≤ n, m ≤ 10^5,
−10^9 ≤ l ≤ r ≤ 10^9,
−10000 ≤ c ≤ 10000

分析

数据范围分析

第一眼看到题目的时候,觉得题目好简单,就是一个标准的前缀和模板题。但是看到数据范围后,才发现好多坑。

第一,有一个无限长的数轴,数轴上每个坐标上的数都是 0。我们看到 x 的范围是 [−10^9, 10^9] ,也就是 2*10^9 大小,如果直接定义这么大的数组,估计要爆。

第二,实际操作的数据是 n,n 的范围是 [1, 10^5],也就是进过了 n 次操作后,需要插入的数据包括 n 个坐标和 m 个 [l, r],因此最多有 3*10^5 个数据是非零的,其他数据都是 0。3*10^5 对比 2*10^9,我们可以发现,非常符合离散化的前提。

第三,由于事前不知道 n 的大小,可以使用 C++ STL 中的 vector,而不是定义数组。

样例数据分析

从原始数据中,我们知道 n=3,m=3。

第一次操作为:x=1,c=2,也就是将坐标为 1 的数据加上 2,那么该点对应的数据变为 2。

第二次操作为:x=3,c=6,也就是将坐标为 3 的数据加上 6,那么该点对应的数据变为 6。

第三次操作为:x=7,c=5,也就是将坐标为 7 的数据加上 5,那么该点对应的数据变为 5。

经过 3 此操作后,我们可以得到当前数据如下图所示。

接下来,我们要进行 m 次区间和操作。

第一次区间为:l=1,r=3。如上图所示,自然对应区间和为 2+6=8。

第二次区间为:l=4,r=6。如上图所示,自然对应区间和为 0。

第三次区间为:l=7,r=8。如上图所示,自然对应区间和为 5。

算法思路

通过上面的分析,我们知道整个操作分两部分,第一部分是加法操作(将某一位置 x 上的数加 c);第二部分是查询操作(询问区间 [l, r] 的区间和)。因此整个算法的基本思路如下:

1、首先读入所有操作数据。

2、离散化数据。将 n 次操作的坐标,m 次查询的 [l, r] 进行离散化。

3、将离散化后的数据进行 n 次加法运算。

4、求离散化后数据前缀和。

5、将 m 次查询的区间和输出。注意 l 和 r 都需要对应到离散化数据。

注意:所有的数据都需要进行离散化。

为了加深对算法的理解,我们利用样例输入,写出整个数据的变化过程。

加入到 vector 后的数据如下,注意将所有的坐标加入到 vector 中。

1 3 7 1 3 4 6 7 8

排序后的数据变为

1 1 3 3 4 6 7 7 8

去重后的数据变为

1 3 4 6 7 8

加法操作后,离散化后数据变为

0 2 6 0 0 5

离散化后前缀和为

0 2 8 8 8 13

第一次查询区间为:l=1,r=3。对应的离散化区间为 [1, 2],自然对应区间和为 8-0=8。

第二次查询区间为:l=4,r=6。对应的离散化区间为 [3, 4],自然对应区间和为 8-8=0。

第三次查询区间为:l=7,r=8。对应的离散化区间为 [5, 6],自然对应区间和为 13-8=5。

AC 参考代码

STL 版本

需要使用到 STL 的 vector、pair 数据类型,以及算法库中的 sort()、unique(),vector 的 earse()。

#include <bits/stdc++.h> 
using namespace std; 
const int MAXN = 3e5+4;
int lsh[MAXN];//离散化后的数据
int sum[MAXN];//前缀和 
int main() {
    int n,m,i;
    cin >> n >> m; 
    vector< int > nums;//所有需要离散化数据
    vector< pair<int, int> > add;//加法操作
    vector< pair<int, int> > query;//查询操作
    //读入所有加法操作
    for (i=0; i<n; i++) {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});
        nums.push_back(x);//x需要离散化
    }
    //读入查询操作
    for (i=0; i<m; i++) {
        int l,r;
        cin >> l >> r;
        query.push_back({l, r});
        nums.push_back(l);
        nums.push_back(r);
    } 
    //排序
    sort(nums.begin(), nums.end());
    //去重
    nums.erase(unique(nums.begin(), nums.end()), nums.end()); 
    //处理加法
    for (auto item:add) {
        int x = lower_bound(nums.begin(), nums.end(), item.first)-nums.begin()+1;
        lsh[x]+=item.second;
    }
 
    //求前缀和
    for (i=1; i<=nums.size(); i++) {
        sum[i]=sum[i-1]+lsh[i];
    }
 
    //查询前缀和
    for (auto item:query) {
        int l = lower_bound(nums.begin(), nums.end(), item.first)-nums.begin()+1;
        int r = lower_bound(nums.begin(), nums.end(), item.second)-nums.begin()+1;
        cout << sum[r]-sum[l-1] << endl;
    } 
    return 0;
}

特别注意:由于使用到了前缀和,数组要下标从 1 开始计算,因此使用 STL 的 lower_bound() 查找二分查找左边界的时候,需要将返回值增加 1。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值