目录
什么是离散化
你是否经历过MLE(超空间)的绝望?你是否还在为数组无法开到那么大而烦恼?只要你学了离散化,一切都不叫问题!
我们举个例子吧!如果有N个数,每个数不超过INT范围,而恰好这道题,你要开最大的数这么多个数组,那岂不就爆了?
如果N在很小的范围,我们就可以离散化了。举个例子,有4个数,5,200,154200,456987123,我们怎么离散化?按他们的大小来排就行啦!离散化后四个数就变成了,1,2,3,4。比如有3个坐标,{100,200},{20,50000},{1,400},离散化后就是{3,4},{2,6},{1,5},这样就大大节省了空间,相应的也节省了时间。
如何离散化
手动版
#include <cstdio>
#include <algorithm>
using namespace std;
int n, r[105];
struct node{
int x, id;
bool operator < (const node &rhs)const {
return x < rhs.x;
}
}a[105];
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i].x);
a[i].id = i;
}
sort(a+1,a+1+n);
int tot = 0;
for(int i = 1; i <= n; i++){
if(a[i].x != a[i-1].x){
r[a[i].id] = ++tot;
}
else {
r[a[i].id] = tot;
}
}
for(int i = 1; i <= n; i++){
printf("%d ", r[i]);
}
return 0;
}
其中的a[i].x!=a[i-1].x我们是在去重。当然我们也可以用STL
STL版
#include <cstdio>
#include <algorithm>
using namespace std;
int n, lsh[105], a[105];
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
lsh[i] = a[i];
}
sort(lsh+1,lsh+1+n);
int cut = unique(lsh+1, lsh+1+n) - lsh - 1;
for(int i = 1; i <= cut; i++){
a[i] = lower_bound(lsh+1, lsh+1+n, a[i]) - lsh;
}
for(int i = 1; i <= n; i++){
printf("%d ", a[i]);
}
return 0;
}
cut是去重后元素的个数,由于unique函数返回的是最后一个元素下一个元素的指针,所以要减去数组头指针,还要减去一个1
如果你是从0开始的,那就不用-1了。lower_bound函数返回的是数组中第一个比a[i]大于或等于的数的指针,如果你是从0开始,
那么要+1,也可以直接用upper_bound函数(返回第一个大于的数的指针)。
小技巧
我在这里给一个小技巧,一个数组A,upper_bound(A+1,A+1+n,5)-lower_bound(A+1,A+1+n,5)就可以找到数组中等于5的个数
离散化的例子
图片面积
大意是给定平面上的n个矩形(坐标为整数,矩形与矩形之间可能有重叠的部分),求其覆盖的总面积。平常的想法就是开一个与二维坐标规模相当的二维Boolean数组模拟矩形的“覆盖”(把矩形所在的位置填上True)。可惜这个想法在这里有些问题,因为这个题目中坐标范围相当大(坐标范围为-10^8到10^8之间的整数)。但我们发现,矩形的数量n<=100远远小于坐标范围。每个矩形会在横纵坐标上各“使用”两个值,100个矩形的坐标也不过用了-10^8到10^8之间的200个值。也就是说,实际有用的值其实只有这么几个。这些值将作为新的坐标值重新划分整个平面,省去中间的若干坐标值没有影响。我们可以将坐标范围“离散化”到1到200之间的数,于是一个200*200的二维数组就足够了。实现方法正如本文开头所说的“排序后处理”。对横坐标(或纵坐标)进行一次排序并映射为1到2n的整数,同时记录新坐标的每两个相邻坐标之间在离散化前实际的距离是多少。这道题同样有优化的余地。(百度百科)
我们来看一张图片吧!
T就是有矩形覆盖的区域,你说如何知道一块区域等于原来的多少,用一个array of size就可以了
你说怎么操作,你自己也能找到规律吧,用循环不就解决了(I believe you!)
坐标离散化
上面就分了6个区域
怎么做,先离散化,由于这道题不一样,一条直线可能就会分成2个区域,所以我们要改变一下离散化的方法。
就是左边和右边包括自己都要存起来,以免出现离散后将区域给减小了。
打个比方吧,如果只有一条直线(平行于Y轴),如果你按照原来的离散方法,我们就会把后直线后面的截掉,而且也会把前面截掉,因为你离散化后直线就在X=1处,我们离散肯定要把范围减小,减到了最后一条直线时就把后面的区域给剪掉了,所以要左右都考虑。
你看下面的代码就会发现,离散后不像上图一样。这就说明了离散不够。经过我反复推敲,我认为只需要考虑前面就可以了,而最后面的直线单独考虑后面。(因为中间直线的后面和下一个直线的前面其实是差不多的)
小技巧
sort(xs.begin(), xs.end());
xs.erase(unique(xs.begin(), xs.end()), xs.end());
此代码可以去重(XS是vector)
另外,find也可以找到某个数的位置,返回的是指针(其实与lower_bound差不多)
代码
借鉴书上的(由于深搜浪费了时间,所以用广搜)
int W, H, N;
int X1[MAX_N], X2[MAX_N], Y1[MAX_N], Y2[MAX_N];
bool fld[MAX_N * 3][MAX_N * 3]
int dx[4] = {0, 0, -1, 1};
int dy[4] = {-1, 1, 0, 0};
// 对 x1 数组和 x2 数组进行坐标离散化,并返回离散化之后的宽度
int compress (int* x1, int* x2, int w)
{
vector<int> xs;
for (int i = 0; i < N; i++)
{
for (int d = -1; d <= 1; d++)
{
int tx1 = x1[i] + d, tx2 = x2[i] + d;
if (tx1 >= 1 && tx1 <= w) xs.push_back(tx1);
if (tx2 >= 1 && tx2 <= w) xs.push_back(tx2);
}
}
sort(xs.begin(), xs.end());
xs.erase(unique(xs.begin(), xs.end()), xs.end());
for (int i = 0; i < N; i++)
{
x1[i] = find(xs.begin(), xs.end(), x1[i]) - xs.begin();
x2[i] = find(xs.begin(), xs.end(), x2[i]) - xs.begin();
}
return xs.size();
}
/*这个函数对坐标进行了压缩:
1. 将坐标的值变成了“这是第几种坐标”(种类和区域个数有关,同个区域的就是同一种坐标),函数返回值是一共有多少种坐标
2. 起止行(列)的前后行(列)若在 w*w 的范围内,则压栈(其实是压入队列)其前后行(列),因为每个黑行对数区域的影响,也就只有它的前后行和本身那行,对别的行是不会有影响的
*/
void solve()
{
// 坐标离散化
W = compress(X1, X2, W);
H = compress(Y1, Y2, H);
// 填充有直线的部分
memset(fld, 0, sizeof(fld));
for (int i = 0; i < N; i++)
for (int y = Y1[i]; y <= Y2[i]; y++)
for (int x = X1[i]; x <= X2[I]; x++)
{
fld[y][x] = true;
}
// 求区域的个数
int ans = 0;
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
{
if (fld[y][x]) continue;
ans++;
// 宽度优先搜索
queue<pair<int, int> > que;
que.push(make_pair(x, y));
while (!que.empty())
{
int sx = que.front().first, sy = que.front().second;
que.pop();
for (int i = 0; i < 4; i++)
{
int tx = sx + dx[i], ty = sy + dy[i];
if (tx < 0 || tx >= W || ty < 0 || ty >= H) continue;
if (fld[ty][tx]) continue;
que.push(make_pair(tx, ty));
fld[ty][tx] = true;
}
}
}
cout << ans << endl;
}