离散化:把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。 ——百度百科
简单来说,离散化,就是为了省空间以及时间。具体做法是:将一个数列中的元素间的空隙吃掉,或者是将元素一个个拎起来塞到一个小的空间中。
适用情况
- 当这个数列中的元素的值的意义只是为了区分元素种类的不同时,可以对其进行离散化。比如记录颜色
- 当这个数列中的元素的值只是为了用来和别的元素比较大小时,可以对其离散化
总的来说,就是不关心数的实际大小,只关心它的相对大小。
作用
一般是用于开一个数组,这个数组的大小与题目中数字的大小有关,但是题目中数字大的一比,数字的个数却很小,这时候就考虑是否将它离散化了。
例题:逆序对
这道题应该大部分人都很熟悉,需要开一个树状数组统计有多少个数比当前的数小,但是数的大小为 1 0 9 10^9 109,显然开不出这个数组,但是发现 我们只关注有多少个数比它小,也就满足了上面的第二个使用情况,并且只有 5 × 1 0 5 5\times 10^5 5×105个数,于是对其离散化,离散化后最大的数也就 5 × 1 0 5 5\times 10^5 5×105,于是就可以开数组了。
如何离散化
一、 最朴素的做法——排序一下重新赋值(时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn))。也就是上面说的,将一个数列中的元素间的空隙吃掉
代码如下:
struct disc{int x,y;};
bool compp(disc x,disc y){return x.x<y.x;}
void discretization(int *darr,int dn)//将darr数组离散化
{
static disc b[maxn];
for(int i=1;i<=dn;i++)
b[i].x=darr[i],b[i].y=i;//存下值以及该值在原数列中的位置
sort(b+1,b+dn+1,compp);//按值排序
static int tot=0;b[0].x=-9666;
for(int i=1;i<=dn;i++)
if(b[i].x!=b[i-1].x)darr[b[i].y]=++tot;//重新编号,注意,值原来一样的现在也应该一样
else darr[b[i].y]=tot;
}
离散化前
356
214
54111
3
12
356~214~54111~3~12
356 214 54111 3 12
离散化后
4
3
5
1
2
4~3~5~1~2
4 3 5 1 2
但是利用 s t l stl stl 还有更方便的写法:
for(int i=1;i<=n;i++)b[i]=a[i]; sort(b+1,b+n+1);
for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+n+1,a[i])-b;
二、 高级一点的做法——散列算法( O ( 玄 学 ) O(玄学) O(玄学))
咋一看好像很高级,其实说白了,就是哈希。
哈希分两种,字符串哈希和数字哈希,这里用到的数字哈希,其实也就是一种离散化。
所谓哈希,就是将这个数mod p,然后再存下来,这样就可以将这些数字存在一个较小的范围内,这个数mod p后得到的值,称作它的hash值(哈希值)。也就是上面说的,将元素一个个拎起来塞到一个小的空间中
但也有很明显的问题,万一有两个数mod p的值一样怎么办?这就是很常见的哈希冲突,解决冲突的办法有很多种,我比较喜欢用的就是将所有hash值相同的元素构成链表。当然还有很多其他的办法,比如说 遇到冲突就将这个元素放到后一个位置,后一个位置还有的话继续往后 这样的。
其实不同的解决办法效率区别不大,主要的效率区别看是看脸,看欧气,如果 p p p 值选的好,冲突较少,那么效率就高了。(这就是上面的时间复杂度的来由233)
顺手贴上代码(可能不大好看但很短呢qaq):
#define mod 1000000007
#define maxn 100010
struct node{
node *next;//链表中的下一个
int x;//值
node(int y):next(NULL),x(y){}
}*hash[maxn];//hash[i]表示hash值为i的那些元素的链表的表头
void add(int x)//加入一个值为x的元素
{
node **t=&hash[x%mod];
while(*t)t=&((*t)->next);//一直往链表的尾部走
*t=new node(x);
}
bool check(int x)//查询hash表中
{
node *now=hash[x%mod];
while(now)if(now->x==x)return true;else now=now->next;
return false;
}
如何使用
然而上面两种离散化的用处都是不一样的,有时候两个都可以用,有时候一个比另一个优秀,还有时候只能用一种,所以如何选择呢?
- 排序法
- 一般用于考虑元素大小的比较的问题,例如上面的逆序对问题。
- 散列算法
- 一般用于判断某个元素是否出现过这样的问题,例如八数码
- 貌似都可以用的
- 例如 收集雪花
- 然而实际上用散列算法比较好,因为排序法可能超时,106诶……
然后……就没啦。
最后给一点小建议:
- 如果题目没告诉你数的大小,而你开的数组大小取决于数的大小,且这个数可以离散化,那么还是离散化比较稳
- 需要用动态开点的权值线段树的话,如果权值可以离散化,那么就尽可能别偷懒,离散化一下可以使线段树常数小很多,很可能逆转TLE变成AC