写在前面
有的时候,我们会发现对于一个序列,它的值域很大,我们算法的复杂度又是 \(\Theta (\mbox{值域})\) 的,但同时我们发现,我们只关心序列中元素的大小关系,却不关心数到底是多少,比如说我们要找到序列中最大元素的位置,在这个问题下,序列 1 2 3 4 5
和序列 6 10 12 18 34
是等价的,这个时候我们可以通过一些方法将后一个序列映射到前一个序列中去,使得序列的值域变小,这个过程就是我们常说的离散化。
下面给出两种离散化的方式。
为了表述方便,用 arr
表示原序列,用 res
表示离散化后的序列,同时规定数组下标从 \(1\) 开始。
方法一
先想一下,我们要离散化的话,只要将一个数的大小排序 rank
和一个数的原位置 place
记录下来即可。获得一个数的 rank
可以通过排序完成,同时我们为了保留原来的位置信息就考虑用结构体来保存,在排序之后我们便只需要按顺序来访问 arr
数组,就可以同时得到原序列中每一个数的大小关系和位置了,这个时候我们只需要将它们一个个放回到 res
中就行了。
int res[100];
struct node {
int num, place;
node(int nn = 0, int pp = 0) {
num = nn;
place = pp;
}
}arr[100];
bool CMP(const node &a, const node &b) {
return a.num < b.num;
}
int main() {
//input
std::sort(arr + 1, arr + 100 + 1, CMP);
for (int i = 1; i < 100; ++i) {
res[arr[i].place] = i;
}
}
注意到上述方法并没有去重,事实上如果要去除重复元素的话,只要把第十九行的后一个 i 改成一个计数器即可。
方法二
方法一相对来讲是比较麻烦的,定义了很多变量,我们可不可以少用一些变量呢?幸运的是, STL 为我们提供了这样的便利。
先来认识两个函数:
unique(start, end);
lower_bound(start, end, key);
unique
函数的作用是去除一个有序序列中的重复元素(保留一个),其作用范围为左闭右开区间 [start, end) ,并返回去重后的序列的最后一个元素的位置。
lower_bound
函数的作用是在左闭右开区间 [start, end) 中寻找第一个大于等于 key
的数,并返回这个数的位置。
如果我们对排完序的序列后使用 lower_bound
,那么我们得到的就是数的大小关系,所以我们可以直接这样子写。
//input
std::sort(arr + 1, arr + n + 1);
cnt = std::unique(arr +1, arr + n + 1) - (arr + 1);
for (int i = 1; i <= n; ++i) {
res[i] = std::lower_bound(arr + 1, arr + cnt + 1, cp[i]) - arr;
}
是不是简洁了很多,但是在这种情况下,我们就需要对原数组进行一次备份(cp
数组),以便后面的复原。
总结
离散化虽然简单,但是是一种需要掌握的重要技巧,放一道例题:[NOI2015]程序自动分析;
还有自己做的一道题:Sorting Trees。
有错误的地方欢迎更正。