OI-tips 离散化

离散化

离散化,一个很简单却很常用的小技巧。

引入

给你 \(n\) 个数,输出每种数出现的个数,满足 \(n\le 10^6\)\(a_i\le 2\times 10^9\)

是不是看上去很简单,直接开一个数组记录个数不就行了。

不过值域的范围是 \(2\times 10^9\),显然正常开数组的话我们是开不下的,而发现 \(n\) 是很小的,在我们可支持的范围内,于是就有了一个映射的思想:我们给每个数标号,记录编号出现的个数,这样就可以将空间复杂度控制在 \(\mathcal{O(n)}\) 级别。这种重新赋一个较小的值的思想就是离散化。

实现

理解了思想,我们再去考虑如何实现。

map 实现

STL 容器中的 map 是蒟蒻们实现离散化的好帮手,它出现的意义本身就是映射:将某种类型的某值映射到某种类型的某值上。

上面提到的“某种类型”可以是 c++ 中的任意数据类型,当然最常用的就是 int 转到 int,即我们的离散化操作。

关于 map 容器我浅讲一点,有兴趣可以自行学习。

定义方法

map 位于 std 库中,若没有在前面声明 using namespace std,需要这样定义(下面均为未声明情况):

std::map<int,int>mp;
// </*原数据类型*/,/*映射后数据类型*/>

使用方法

很简单,就和普通的数组一样。

int a=1000000000;
mp[a]=1;
// 将 a 的值映射为 1

例题实现

#include<bits/stdc++.h>
int n,tot,a[1000005],sum[1000005];
bool vis[1000005];
std::map<int,int>mp;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if(!mp[a[i]]) mp[a[i]]=++tot;
		// 离散化
		sum[mp[a[i]]]++;
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[mp[a[i]]]) continue;
		vis[mp[a[i]]]=1;
		printf("%d %d\n",a[i],sum[mp[a[i]]]);
	}
	return 0;
}

优化

可以将 map 换成 unordered_map,会有一点加速效果。

数组实现

众所周知,stl 容器的速度都比较玄学,因此在面对需要大量运用离散化时,我们需要一种较稳定的实现方法。

回归主题,我们思考一下,离散化的本质是什么?是去重。stl 算法中存在一个操作 unique,作用是去除容器中相邻的重复元素,好像跟我们的目的很像,但发现其去重的对象只是相邻元素,于是我们在去重前需要先将原序列排序。这就有了离散化=排序+去重的说法。

这样离散化后的数据还能反映数值的大小关系,这就大大增强了这种方法的泛用性。

注意: 由于我们改变了序列的顺序,我们获取每个元素离散化后的值时需要用到 lower_bound 操作,如果原来元素的值对后续没有影响,这个操作最好在离散化后立即进行。(不然像我一样用的时候再查就直接给复杂度乘了一个 \(\log\)

for(int i=1;i<=n;i++) b[i]=a[i];
tot=n;
sort(b+1,b+1+tot);
tot=unique(b+1,b+1+tot)-b-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+tot,a[i])-b;

例题实现

#include<bits/stdc++.h>
int a[1000005],b[1000005],num[1000005],n,tot;
bool vis[1000005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
	tot=n;
	sort(b+1,b+1+tot);
	// 先排序
	tot=unique(b+1,b+1+tot)-b-1;
	// 再去重,tot 直接赋值为不重复的数的个数
	for(int i=1;i<=n;i++)
	{
		int id=lower_bound(b+1,b+1+tot,a[i])-b;
		num[id]++;
	}
	for(int i=1;i<=n;i++)
	{
		int id=lower_bound(b+1,b+1+tot,a[i])-b;
		if(vis[id]) continue;
		vis[id]=1;
		printf("%d %d\n",a[i],num[id]);
	}
	return 0;
}

离散化虽然基础,但细节也挺多的。

操作难度 时间复杂度 空间复杂度
map 简单 \(\mathcal{O(n\log n)}\)(仅离散化) -
数组 不难 \(\mathcal{O(n\log n)}\)(稳定) \(\mathcal{O(n)}\)

不过实测下来还是数组更快一点,因为每次引用 map 都是 \(\mathcal{O(\log n)}\) 的,多几次就慢了。

本文代码全部线上手打,有问题请指出,感谢支持。


完结撒花~

posted @ 2024-08-18 19:29  DrRatio  阅读(45)  评论(3编辑  收藏  举报