离散化详解

离散化:把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。 ——百度百科

简单来说,离散化,就是为了省空间以及时间。具体做法是:将一个数列中的元素间的空隙吃掉,或者是将元素一个个拎起来塞到一个小的空间中。

适用情况

  1. 当这个数列中的元素的值的意义只是为了区分元素种类的不同时,可以对其进行离散化。比如记录颜色
  2. 当这个数列中的元素的值只是为了用来和别的元素比较大小时,可以对其离散化

总的来说,就是不关心数的实际大小,只关心它的相对大小

作用

一般是用于开一个数组,这个数组的大小与题目中数字的大小有关,但是题目中数字大的一比,数字的个数却很小,这时候就考虑是否将它离散化了。

例题:逆序对

这道题应该大部分人都很熟悉,需要开一个树状数组统计有多少个数比当前的数小,但是数的大小为 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;
}

如何使用

然而上面两种离散化的用处都是不一样的,有时候两个都可以用,有时候一个比另一个优秀,还有时候只能用一种,所以如何选择呢?

  1. 排序法
  • 一般用于考虑元素大小的比较的问题,例如上面的逆序对问题。
  1. 散列算法
  • 一般用于判断某个元素是否出现过这样的问题,例如八数码
  1. 貌似都可以用的
  • 例如 收集雪花
  • 然而实际上用散列算法比较好,因为排序法可能超时,106诶……

然后……就没啦。

最后给一点小建议:

  1. 如果题目没告诉你数的大小,而你开的数组大小取决于数的大小,且这个数可以离散化,那么还是离散化比较稳
  2. 需要用动态开点的权值线段树的话,如果权值可以离散化,那么就尽可能别偷懒,离散化一下可以使线段树常数小很多,很可能逆转TLE变成AC
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值