前言
当有效数据量可控,但数值落在的数值空间非常大的时候,离散化思想就可以派上用场了。
一、引入例题
题目描述
- 桌面上放了N个平行于坐标轴的矩形,这N个矩形可能有互相覆盖的部分,求它们组成的图形的面积。
输入格式
- 输入第一行为一个数N
(1≤N≤100)
,表示矩形的数量。下面N行,每行四个整数,分别表示每个矩形的左下角和右上角的坐标,坐标范围为 − 1 0 4 -10^4 −104 到 1 0 4 10^4 104 之间的整数。
输出格式
- 输出只有一行,一个整数,表示图形的面积。
分析
每输入一个矩形的时候,在这个矩形的位置填充 true
,最后再相加。这个时候不加优化的 暴力
最坏的情况下这个时候空间复杂度大概为
O
(
4
×
1
0
8
)
O(4\times10^8)
O(4×108),这还是可以接受的,代码如下: 这里很好理解我就不解释啦QAQ
#include<cstdio>
#include<algorithm>
using namespace std;
bool map[1001][1001];
int n,x1,y1,x2,y2,xx,yy,ans;
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++) {
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
if(x1>x2)swap(x1,x2);
if(y1>y2)swap(y1,y2);
xx=max(xx,x2);
yy=max(yy,y2);
for(int x=x1;x<=x2;x++)
for(int y=y1;y<=y2;y++)
map[x][y]=1;
}
for(int x=0;x<=xx;x++)
for(int y=0;y<=yy;y++)
ans=ans+map[x][y];
printf("%d",ans);
return 0;
}
但是如果题目将坐标范围扩大到
−
1
0
8
-10^8
−108 到
1
0
8
10^8
108 的话时间和空间都会爆掉, 暴力不可解
。
优化
时间
从时间的角度来优化的话,我们可以通过批量的填充来节约时间,也就是说将整个大的矩形转化成小的矩形:(虽然实际情况可能并不是这样)
若每次这样填充,在最后遍历的时候,时间效率会大大提高。
空间
从空间角度来看,我们可以直接储存每个小块,再储存他们的面积:
此时所需要的空间由原来的
7
×
10
7\times10
7×10 变成了
2
×
4
×
4
2\times4\times4
2×4×4。哇,你真了不起
这里附上代码:
//矩形覆盖,离散化处理
#include<cstdio>
#include<algorithm>
#define nn 1001
using namespace std;
struct aty{
int x1,x2,y1,y2;
} a[nn];
struct bty{
long long N,xy;
}b[2*nn],c[2*nn];
bool cmp(bty a,bty b){ return a.xy<b.xy;}
long long xx[nn],yy[nn],x1,y1,x2,y2,nowx,nowy,ans;
bool map[201][201];
int n,newx,newy;
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
b[i].N=b[n+i].N=c[i].N=c[i+n].N=i;
b[i].xy=x1;b[i+n].xy=x2;
c[i].xy=y1;c[i+n].xy=y2;
}
sort(b+0,b+2*n,cmp);
sort(c+0,c+2*n,cmp); // 排序完后用下标代替左右x、y的值
for(int i=0;i<2*n;i++){
if(nowx!=b[i].xy) nowx=b[i].xy,newx++,xx[newx]=nowx;
if(a[b[i].N].x1==0) a[b[i].N].x1=newx;
else a[b[i].N].x2=newx;
if(nowy!=c[i].xy) nowy=c[i].xy,newy++,yy[newy]=nowy;
if(a[c[i].N].y1==0) a[c[i].N].y1=newy;
else a[c[i].N].y2=newy;
}
for(int i=0;i<n;i++){
for(int x=a[i].x1+1;x<=a[i].x2;x++)
for(int y=a[i].y1+1;y<=a[i].y2;y++)
map[x][y]=1;
}
for(int x=2;x<=newx;x++)
for(int y=2;y<=newy;y++)
if(map[x][y]==1)
ans+=(xx[x]-xx[x-1])*(yy[y]-yy[y-1]);
printf("%d",ans);
return 0;
}
优化总结
经过时间和空间的优化,这道题的时间成本就可以降到可以接受的程度,而这种优化思想,就叫做离散化思想
。
二、离散化思想总结
概述
- 离散化,就是把无限空间中有限的个体映射到有限的空间中去,以提高算法的时空效率。
- 很多算法的复杂度与数据中的最大值有关,比如用数组实现的一对一标记。时常会遇到这种情况:数据的范围非常大或者其中含有负数,但数据本身的个数并不是很多(远小于数据范围)。在这种情况下,如果每个数据元素的具体值并不重要,重要的是他们之间的大小关系的话,我们可以先对这些数据进行离散化,使数据中的最大值尽可能小且保证所有数据都是正数。
预期功能
离散化的原理和实现都很简单。为了确保不出错且尽可能地提高效率,我们希望离散化能实现以下几种功能:
1.保证离散化后的数据非负且尽可能的小
2.离散化后各数据项之间的大小关系不变,原本相等的也要保持相等。
由此,找出数据项在原序列中从小到大排第几就是离散化的关键
。
思路
可以通过下面的方法以O(nlogn)的时间复杂度完成离散化,n为序列长度。
1.对原序列进行排序,使其按升序排列。
2.去掉序列中重复的元素。
3.此时序列中各位置的值和位置的序号就是离散化的映射方式。
例题
这里有一个小小的题目很好的体现了离散化思想:
- 例如:对于序列105,35,35,79,-7,排序并去重后变为-7,35,79,105,由此就得到了对应关系-7->1, 35->2, 79->3, 105->4。
int n, a[maxn], t[maxn];
//这里以下标1为序列的起点,一般情况下从0开始也可以
for(int i = 1;i <= n;i++){
scanf("%d", &a[i]);
t[i] = a[i];//t是一个临时数组,用来得到离散化的映射关系
}
//下面使用了STL中的sort(排序),unique(去重),lower_bound(查找)函数
sort(t + 1, t + n + 1);//排序
int m = unique(t + 1, t + 1 + n) - t - 1;//去重,并获得去重后的长度m
for(int i = 1;i <= n;i++){
a[i] = lower_bound(t + 1, t + 1 + m, a[i]) - t;//通过二分查找,快速地把元素和映射对应起来
}
总结
实际上这非常好理解QAQ,所以…我先溜啦,芜湖~。