离散化学习

目录

前言

一、离散化介绍

1.1 什么是离散化

1.2 常见离散化处理

1.2.1 简单离散化

1.2.2 去重离散化

1.2.3 离散化后按值查找

二、例题练习

2.1 离散化 + 区间合并

题目描述

思路分析

代码模板

2.2 离散化 + 前缀和

题目描述

思路分析

代码模板


前言

以前学算法挖的坑,终会在一场比赛里给你埋了。

还是要打好基础,学不会的算法,当时你可以先放放,因为可能有的前置算法你没学到,但是后面一定记得回头补,别到了赛场考到了才发现没学23333

含泪补基础。

一、离散化介绍

1.1 什么是离散化

当数据范围在10^6以内时,我们通常开一个数组就能直接映射,如:让值为i存到a[i]位置

数据范围很大时,如1~10^9、1~10^18次方,但是数据量很少,如10^5个数,此时我们就不能直接映射。

以数据范围10^18,数据量10^5为例。

一是内存不够,不可能开一个a[10^18]的数组。

二是就算能开,遍历一遍时间复杂度也是10^18。

此时我们就需要一个辅助数组,把所有数字紧挨着放到一个数组中,就能压缩到10^5,就能支持后续操作。

通常不会单独考离散化,会和其他知识点一块考,比如离散化+区间合并、离散化+前缀和、离散化+差分 等等。

1.2 常见离散化处理

1.2.1 简单离散化

假如有不重复的数字1、1000、10000、10^9,我们可以直接挨着存到数组a中

数组下标0123
a[i]110001000010^9

但是如果有重复数字呢?我们一般是不能存重复数字的。当我们想往数组存一个数时,如何判断这个数是否已经存在?或者说 如何快速去重得到一个不重复的序列?

1.2.2 去重离散化

对于判断当前想存的数字是否重复,

简单方法O(n^2):暴力枚举,遍历一遍数组看是否有重复的,一般数据量10^3才能用。

高效方法O(nlogn):这里用c++的函数实现,先把值全存进来,再去掉重复的。

先排序O(nlogn),再去重O(n)

sort(a.begin(),a.end()); //将所有的原位置排序 
alls.erase(unique(a.begin(),a.end()),a.end()); //去掉a中重复的位置

具体来说就是先从小到大排序,然后unique(a.begin(),a.end())会把不重复的元素放到数组前面,然后返回不重复元素序列的最后一位,这个元素之后的都是重复数字,直接用erase()函数删去即可

我们就可以得到一个从小到大排列的 无重复的 离散化的数组

但是还有一个问题,我们如何快速找到原来的值离散化后的位置?

1.2.3 离散化后按值查找

现在已经有 有序的离散化的数组a[N],我们想找 值为x 离散化后的数组下标

简单方法O(n^2):暴力枚举,直接遍历数组a寻找值为x的下标,找一个值复杂度为O(n),找n个值的话,时间复杂度来到O(n^2)。数据范围10^3才能用

高效方法O(nlogn):既然数列a已经有序,我们当然可以二分查找,可以高效的找到值为x的数组下标,找n个值的话,时间复杂度就是O(nlogn)

int find(int x)//二分查找,用来寻找原位置对应的离散后的位置 
{
	int l = 0, r = a.size() - 1;
	while(l < r)
	{
		int mid = l + r >> 1;
		if(a[mid] >= x)
			r = mid;
		else
			l = mid + 1;
	}
	return r + 1; //+1是为了从1开始,即映射到1,2,3到n
}

到这里我们已经学会了基本的离散化的操作。作者去做题了,如果遇到的题目还有什么常见的操作我会继续补充博客。下面是一些例题的练习。

二、例题练习

2.1 离散化 + 区间合并

题目描述

题目链接:P1496 火烧赤壁 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路分析

首先给定n个左闭右开的区间,而且是在一维上的,求n个区间的长度和,这里基本就是裸的区间合并,然后我们注意到范围是-10^9 ~ 10^9,我们需要稍微离散化下

这里只是简单用了一下离散化,不需要去重、查找什么的,直接开个pair<int, int>类型的数组存下每个区间即可。

代码模板

#include<iostream>
#include<cstring>
#include<algorithm>

#define x first //左边界 
#define y second //右边界 

using namespace std;

const int N = 2e4 + 10;
const int INF = 0x3f3f3f3f;

typedef long long ll;
typedef pair<int, int> PII;

int n;
PII a[N]; //定义一个pair类型数组,记录左右边界 

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
		scanf("%d %d", &a[i].x, &a[i].y);
	
	sort(a + 1, a + n + 1); //区间合并前要先按左端点从小到大排序
	
	int l = -INF, r = -INF; //初始左右边界 
	ll res = 0; //记录答案 
	for(int i = 1; i <= n; i++)
	{
		if(a[i].x > r) //不相交
		{
			res += r - l;
			l = a[i].x;
			r = a[i].y;
		} 
		else
		{
			r = max(a[i].y, r);
			if(i == n) //如果是最后一个了 直接结算
				res += r - l; 
		}
	} 
	printf("%lld", res);
	return 0;
}

2.2 离散化 + 前缀和

题目描述

题目链接:802. 区间和 - AcWing题库

思路分析

数据范围是-10^9 ~ 10^9,数据量是10^5,我们可以先把要用到的点(包括操作和查询)存下来,把要加的位置和值一块存下来,然后把位置离散化去重,根据得到的离散化数组,再执行加值操作,然后求一个前缀和即可。

代码模板

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
const int N = 3e5 + 10;

int n, m;
int a[N], s[N]; //a记录离散后的位置上的值,s前缀和

vector<int> alls; //记录所有要用到的位置
vector<PII> add, query; //add记录原位置和要加的数,query记录查询的位置区间

int find(int 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; //+1是为了从1开始,即映射到1,2,3......n
}

int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++)// 加值操作
    {
        int x, c;
        scanf("%d %d",&x, &c);
        add.push_back({x, c}); //将原位置和要加的数加入add 
        alls.push_back(x); //记下要用到的位置
    }
    for(int i = 1; i <= m; i++) // 查询操作
    {
        int l, r;
        scanf("%d %d", &l, &r);
        alls.push_back(l); //记下要用到的位置
        alls.push_back(r);
        query.push_back({l, r}); // 记录查询的位置区间
    }
    
    sort(alls.begin(), alls.end()); //排序
    alls.erase(unique(alls.begin(), alls.end()), alls.end()); //去重
    
    for(auto it : add) // 执行加值操作, add 第一个值是位置,第二个值是加数
    {
        int x = find(it.x); //寻找原位置离散后的数组下标
        a[x] += it.y; //然后加值
    }
    
    for(int i = 1; i <= alls.size(); i++) //预处理一下前缀和, alls是去重后的
        s[i] = s[i - 1] + a[i];
        
    for(auto it : query) // 处理查询操作
    {
        int l = find(it.x); //寻找原位置离散后的数组下标
        int r = find(it.y);
        printf("%d\n", s[r] - s[l - 1]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值