学习笔记:离散化

离散化

引入

离散化,就是当我们只关心数据的大小关系时,用排名代替原数据进行处理的一种预处理方法。离散化把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率,即:在不改变数据相对大小的条件下,对数据进行相应的缩小。本质上是一种哈希,它在保持原序列大小关系的前提下把其映射成正整数。当原数据很大或含有负数、小数时,难以表示为数组下标,一些算法和数据结构无法运作,这时我们就可以考虑将其离散化。

实际上,离散化可以用 STL 较简单地完成。

实现

首先,我们假设有这样一个序列:\left\{1,2023,114514,1919810,1145141919810\right\}

如果要对这个序列进行桶排序的话,通常情况下需要 1145141919810 个桶!如果强行开这么大的数组的话:

tsqtsqtsq@MateBook:/mnt/c/Users/tsqtsqtsq/OIer/work$ g++ -o a lisan.cpp -O2 -std=c++14 -static
/usr/bin/ld: failed to convert GOTPCREL relocation; relink with --no-relax
collect2: error: ld returned 1 exit status
tsqtsqtsq@MateBook/mnt/c/Users/tsqtsqtsq/OIer/work$

那应该是会寄的……

显然这里面有大量空间是被浪费掉的。

但如果我们考虑离散化的话,就只需要 5 个桶!

tsqtsqtsq@MateBook:/mnt/c/Users/tsqtsqtsq/OIer/work$ g++ -o a lisan.cpp -O2 -std=c++14 -static
tsqtsqtsq@MateBook:/mnt/c/Users/tsqtsqtsq/OIer/work$

那就可以正常编译了!

那么如何实现离散化呢?

首先,我们显然还是需要原数据的,所以先复制一份:

for(int i = 1 ; i <= n ; i ++)b[i] = a[i]; // 将原数组复制一份

接着我们再排序并去重:

    sort(b + 1, b + n + 1); // 排序
   len = unique(b + 1, b + n + 1) - b - 1; // 去重,其中 len 为不重复元素的数量

std::unique() 的返回值是一个迭代器(对于数组来说就是指针了),它表示去重后容器中不重复序列的最后一个元素的下一个元素。所以可以这样作差求得不重复元素的数量。

再用一个数组,储存 a 中每个元素在 b 中的排名:

    for(int i = 1 ; i <= n ; i ++)c[i] = lower_bound(b, b + len, a[i]) - b; // 查找离散化之后的位置

这样我们就实现了原序列的离散化。

因为排序和 n 次二分查找的复杂度都是 O(n\log ⁡n) ,所以离散化的复杂度也是 O(n\log ⁡n)。完整代码很短:

#include <iostream>
#include <algorithm>
#define int __int128
#define MAXN 100005
using namespace std;
int n, len;
int a[MAXN], b[MAXN], c[MAXN];
int read(){
   int t = 1, x = 0;char ch = getchar();
   while(!isdigit(ch)){if(ch == '-')t = -1;ch = getchar();}
   while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
   return x * t;
}
void write(int x){
   if(x < 0){putchar('-');x = -x;}
   if(x >= 10)write(x / 10);
   putchar(x % 10 ^ 48);
}
signed main(){
   n = read();
   for(int i = 1 ; i <= n ; i ++)a[i] = read();
   for(int i = 1 ; i <= n ; i ++)b[i] = a[i]; // 将原数组复制一份
   sort(b + 1, b + n + 1); // 排序
   len = unique(b + 1, b + n + 1) - b + 1; // 去重,其中 len 为不重复元素的数量
   for(int i = 1 ; i <= n ; i ++)c[i] = lower_bound(b + 1, b + n + 1, a[i]) - b; // 查找离散化之后的位置
   for(int i = 1 ; i <= n ; i ++){
       if(i != 1)putchar(' ');
       write(c[i]);
  }
   putchar('\n');return 0;
}

输出结果如下:

tsqtsqtsq@MateBook:/mnt/c/Users/tsqtsqtsq/OIer/work$ g++ -o a lisan.cpp -O2 -std=c++14 -static
tsqtsqtsq@MateBook:/mnt/c/Users/tsqtsqtsq/OIer/work$ time ./a
5
1 2023 114514 1919810 1145141919810
1 2 3 4 5

real   0m0.263s
user   0m0.000s
sys     0m0.023s
tsqtsqtsq@MateBook:/mnt/c/Users/tsqtsqtsq/OIer/work$ time ./a
10
-1145141919810 -1919810 -114514 -100 1 2 3 114514 1919810 1145141919810
1 2 3 4 5 6 7 8 9 10

real   0m0.256s
user   0m0.000s
sys     0m0.023s
tsqtsqtsq@MateBook:/mnt/c/Users/tsqtsqtsq/OIer/work$

离散化也不一定要从小到大排序,有时候也需要从大到小。这时在排序和查找时相应地加上 greater<int>() 就可以了。

posted @ 2023-10-13 19:15  tsqtsqtsq  阅读(101)  评论(0)    收藏  举报  来源