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)}\) 的,多几次就慢了。
本文代码全部线上手打,有问题请指出,感谢支持。
完结撒花~