转载请说明出处:http://blog.csdn.net/lifajun90/article/details/9076453
在计算机程序设计中的离散化是一种程序设计的技巧,和数学中的离散化有一定的区别。
离散化的基本思想是把无限空间中有限的要素映射到有限的空间中去,同时不改变要素之间的相对关系。
如我有一组实数[1.1, 3.5, 4.4, 100.2, 999999.9, ..., 10000000000],该组实数总共有100个,由于实数空间的数是无限的,但是这里我们只考虑这个空间里面的100个数,所以我们就可以把这些数映射为整数空间的[1, 2, ..., 100],我们把1对应为1.1, 2对应为3.5,..., 100对应为10000000000(操作1就是操作1.1, 操作2就是操作3.5.....),这样我们就把实数空间中的一组数映射为了整数空间的一组数,同时它们的大小关系没有发生变化。
我们再来看一个实数域上线段离散化的例子。如有三条线段(1.0, 2.5), (2.0, 100.0), (50.0, 1000.0),现在我们要求这些线段总共覆盖的区间有多长?由于数据是实数的,我们没法用一个一维数组来模拟它们所覆盖的区间,所以我们先把这些实数离散化,做如下映射[1.0, 2.0, 2.5, 50.0, 100.0, 1000.0]->[1, 2, 3, 4, 5, 6],这样区间就变为(1, 3), (2, 5), (4, 6),注意这样区间之间的覆盖关系没有发生变化,如(1.0, 2.5)和(2.0, 100.0)原来有覆盖,现在变为(1, 3) (2, 5), 它们同样有覆盖关系。现在我们就可以用数组来模拟了,我们知道它覆盖了整数域上[1,6]的区间,而1对应着1.0,6对应着1000.0,所以这三条线段覆盖的总长度就是1000.0。
那么怎么样来对数据进行离散化呢?
其实很简单,首先用一个数组来收集所有的数据,然后对这些数据进行排序去重,把每个数据对应的下标作为它们离散化后的值就可以了。如把区间[10, 20000]和[20000, 100000]离散化,我们先用一个数组收集这些数字并去重得到数组[10, 20000, 100000],然后10就对应于1, 20000就对应于2, 100000就对应于3,对于某个特定的数字可以通过在数组中进行二分查找来获得它的下标。
那么为什么要把数据离散化呢?
这里先给出答案,后文再通过一个例子详细说明。数据通过离散化之后,可以改善程序的时间和空间复杂度,甚至完成一些看似无法完成的任务。
下面来看一个例子。
先给出题目链接:http://soj.me/1045
题目的问题简化描述如下:现在有一个桌面,桌面上有一些文档(个数N<=500),桌面和文档都是矩形形状的,桌面上的文档之间可能有覆盖关系。把桌面看成一个二维坐标系,现在给出一系列的文档(以左上角坐标和长、宽的形式,0<w<=2000, 0<h<=1000),要求的是这些文档总共覆盖的桌面面积。例如有三个文档(0 0 2 2), (2 2 2 2), (1 1 2 2)分别表示以(0, 0)为左上角的长宽都为2的矩形文档,和以(2 2)为左上角的长宽都为2的文档以及以(1, 1)为左上角的长宽都为2的文档,那么很明显它们覆盖的面积为10.
那么这个问题的直观解法很简单:首先维护一个二维数组来模拟整个平面,然后对于每个到来的文档,直接把它所覆盖的区域置为1,最后扫描一遍这个二维数组,把所有被覆盖过的位置加起来就是答案。代码如下:
memset(used, false, sizeof(used)); scanf("%d", &n); while(n--) { scanf("%d %d %d %d", &x, &y, &w, &h); for(int i=x; i<x+w; ++i) for(int j=y; j<y+h; ++j) used[i][j] = true; } int ans = 0; for(int i=0; i<2000; ++i) for(int j=0; j<1000; ++j) ans += used[i][j]; printf("%d\n", ans);
这样,我们可能就不能简单的直接把矩形的坐标对应为数组的下标了,因为我们没办法开那么大的数组,对于实数来说这也是不可能的。
对于这个问题,我们可以这样来想,虽然说给出的矩形的长宽范围很大,但是由于矩形的数量是相对较少的,如例子中的N<=500,那么现在所有的矩形它们用到的x坐标最多就是2*N=1000个,y坐标也一样。这就是典型的离散化适用的范围:把无限(或近似无限)空间中的有限元素映射到有限的空间中去。
如现在有两个矩形(0 0 100000 100000), (50000 50000 100000 100000), 注意矩形表示方法,它们的左上角和右下角表示法其实是(0 0 100000 100000)和(50000 50000 150000 150000)现在我们作如下映射:[0, 50000, 100000, 150000]->[1, 2, 3, 4],那么两个矩形就可以变为(1 1 3 3)和(2 2 4 4),现在这两个小矩形的面积求法就可以用上面例子中的模拟来求了,最后我们再来收集这些面积,如(1,1)这个格子被覆盖,其实它表示的面积是(50000-0)*(50000-0) = 250000,其它格子类似。
这样我们就通过离散化把一个大范围空间的点映射为了小范围空间的点从而解决了问题。
下面是本题用离散化来解决的完整代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 505 struct Rect { int x1, y1, x2, y2; }rect[N]; int x[2*N], y[2*N]; bool map[N][N]; int bs(int *a, int key, int n) { int left = 0, right = n-1, mid; while(left <= right) { mid = (left + right)>>1; if(a[mid] == key) return mid; else if(a[mid] < key) left = mid + 1; else right = mid - 1; } return -1; } int main() { int n, lx, ly, w, h, T; scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i=0; i<n; ++i) { scanf("%d %d %d %d", &lx, &ly, &w, &h); rect[i].x1 = lx, rect[i].y1 = ly, rect[i].x2 = lx+w, rect[i].y2 = ly + h; x[2*i] = lx, x[2*i+1] = lx+w, y[2*i] = ly, y[2*i+1] = ly + h; } sort(x, x+2*n); sort(y, y+2*n); int cnt1 = 1, cnt2 = 1; for(int i=1; i<2*n; ++i) { if(x[i] != x[i-1]) x[cnt1++] = x[i]; if(y[i] != y[i-1]) y[cnt2++] = y[i]; } memset(map, false, sizeof(map)); for(int i=0; i<n; ++i) { int x1 = bs(x, rect[i].x1, cnt1); int y1 = bs(y, rect[i].y1, cnt2); int x2 = bs(x, rect[i].x2, cnt1); int y2 = bs(y, rect[i].y2, cnt2); for(int i=x1; i<x2; ++i) for(int j=y1; j<y2; ++j) map[i][j] = true; } int ans = 0; for(int i=0; i<cnt1; ++i) { for(int j=0; j<cnt2; ++j) if(map[i][j]) ans += (x[i+1]-x[i]) * (y[j+1]-y[j]); } printf("%d\n", ans); } return 0; }