坐标离散化 voj1056

离散化

1.

离散化是程序设计中一个常用的技巧,它可以有效的降低时间和空间复杂度。其基本思想就是在众多可能的情况中,只考虑需要用的值。离散化可以改进一个低效的算法,甚至实现根本不可能实现的算法。要掌握这个思想,必须从大量的题目中理解此方法的特点。例如,在建造线段树空间不够的情况下,可以考虑离散化。——来自百度百科

下面举几个例子:

-有些数据本身很大, 自身无法作为数组的下标保存对应的属性。如果这时只是需要这堆数据的相对属性, 那么可以对其进行离散化处理。当数据只与它们之间的相对大小有关,而与具体是多少无关时,可以进行离散化。
设有4个数:
1234567、123456789、12345678、123456
排序:123456<1234567<12345678<123456789
=>1<2<3<4
那么这4个数可以表示成:2、4、3、1。

例子1:
在这里插入图片描述

这个问题难就难在,这个矩形可以倾斜放置(边可以不用平行于坐标轴)。
在这里插入图片描述
矩形可以斜着放是一个难题,因为我们不知道它倾斜的角度α。
----- 怎么处理呢?-----
我们先假设角度为已知α , 那么问题就简单了 , 我们可以很清楚的知道当矩形面积最小时 , 矩形的四条边一定挨着某个点 , 也就是说 , 当知道四条边的的斜率的时候 , 就可以通过四个点集来不断逼近进而获得一个四边形 , 至于如何实现这个不用知道 ,总之我们通过一定的方法得到了 ,下面要说的过程才是重点。

既然我们知道了逼近,那么算法也明确了:通过枚举矩形的倾角,计算每个矩形的面积 ,再比较得出最小的就行了。

先不说想法正确性,这个算法是不可能实现的。因为 矩形 的倾角是个实数,有无数种情况 , 你永远无法枚举到底。我们可以说, “ 倾角 ” 是一个 “ 连续的 ” 变量,这也是我们无法枚举到底的原因。
-----怎么办呢?-----
离散化

把这个 “ 连续的 ” 变量变成一个一个的值 ,变成一个 “ 离散的 ” 变量 。这个过程也就是所谓的离散化。

继续那个题,我们还可以证明 ,当面积最小的时候,至少有一条边上有2个或者2个以上的点。你可以试想一下,当每条边上只有一个点时,我们总可以将矩形扭转一下,使矩形的面积更小(同时点也在矩形内)。因此,我们可以得到矩形的某条边一定是这些点中两个点的连线
,那么这条边的斜率也等于两点的连线的斜率。那么我们就可以枚举所有任意两点间连线的斜率,这样,我们就将斜率 “ 离散 ” 了。

这样就达到了我们开始时的目的了。

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
 
#define esp 1e-6
 
using namespace std;
 
//点结构 
typedef struct pnode
{
	double x,y,d;
	pnode( double a, double b ){x = a;y = b;}
	pnode(){}
}point;
point P[ 1005 ];
 
//直线结构 
typedef struct lnode
{
	double x,y,dx,dy;
	lnode( point a, point b ){x = a.x;y = a.y;dx = b.x-a.x;dy = b.y-a.y;}
	lnode(){}
}line;
 
//叉乘ab*ac 
double crossproduct( point a, point b, point c )
{
	return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y); 
}
 
//点到点距离 
double dist( point a, point b )
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
} 
 
//点到直线距离 
double dist( point p, point a, point b )
{
	return fabs(crossproduct( p, a, b )/dist( a, b ));
} 
 
//坐标排序 
bool cmp1( point a, point b )
{
	return (a.x == b.x)?(a.y < b.y):(a.x < b.x);
}
 
//级角排序 
bool cmp2( point a, point b )
{
	double cp = crossproduct( P[0], a, b );
	if ( cp == 0 ) return a.d < b.d;
	else return cp > 0;
}
 
//叉乘判断平行  
double judge1( point a, point b, point c, point d )  
{  
    return crossproduct( a, b, point( a.x+d.x-c.x, a.y+d.y-c.y ) );
}  
 
//点乘判断垂直 
double judge2( point a, point b, point c, point d )
{
	return ((b.x-a.x)*(d.x-c.x)+(b.y-a.y)*(d.y-c.y));
} 
 
double Graham( int n )
{
	if ( n < 3 ) return 0.0;
	sort( P+0, P+n, cmp1 );
	for ( int i = 1 ; i < n ; ++ i )
		P[i].d = dist( P[0], P[i] );
	sort( P+1, P+n, cmp2 );
	
	//计算凸包 
	int top = 1;
	for ( int i = 2 ; i < n ; ++ i ) {
		while ( top > 0 && crossproduct( P[top-1], P[top], P[i] ) < esp ) -- top;
		P[++ top] = P[i];
	}
	P[++ top] = P[0];
	if ( top < 3 ) return 0.0;
	
	//旋转卡壳
	int L2 = 0,L3 = 0,L4 = 0;
	for ( int i = 0 ; i < top ; ++ i ) {
		if ( P[i].y <= P[L2].y ) L2 = i;
		if ( P[i].x >= P[L3].x ) L3 = i;
		if ( P[i].y >= P[L4].y ) L4 = i;
	}
	
	double Min = (P[0].x-P[L3].x)*(P[L2].y-P[L4].y);
	for ( int L1 = 0 ; L1 < top ; ++ L1 ) {
		
		//旋转平行边 
		while ( judge1( P[L1], P[L1+1], P[L3], P[L3+1] ) > esp )
            L3 = (L3+1)%top;
		
		//旋转垂直边 
		while ( L2 != L3 && judge2( P[L1], P[L1+1], P[L2], P[L2+1] ) > esp ) 
			L2 = (L2+1)%top;
		while ( L4 != L1 && judge2( P[L1+1], P[L1], P[L4], P[L4+1] ) > esp ) 
			L4 = (L4+1)%top;
		
		double D = dist( P[L3], P[L1], P[L1+1] );
		double L = dist( P[L2], P[L4], point( P[L4].x-P[L1+1].y+P[L1].y, P[L4].y+P[L1+1].x-P[L1].x ) );
		if ( Min > D*L ) Min = D*L;
	}
	return Min;
}
 
int main()
{
	int n;
	while ( ~scanf("%d",&n) && n ) {
		for ( int i = 0 ; i < n ; ++ i )
			scanf("%lf%lf",&P[i].x,&P[i].y);
		
		printf("%.4lf\n",Graham( n ));
	}
	return 0;
}

还有一些题目, 对于某些坐标虽然已经是整数(已经是离散的了)但范围极大的问题,我们也可以用离散化的思想缩小这个规模。

例如 VOJ 1056(离散化经典问题)
在这里插入图片描述
因为题目中的坐标范围相当的大 ,-108—108,我们发现我们的矩阵数量远小于我们的坐标范围,实际用到的只有100个矩阵的坐标也不过是-108—108中的200个值.
于是我们可以将坐标"离散化"到1~200之间的数,所以建一个200*200的数组就行了.
实现方法为 对横坐标(或纵坐标)进行一次排序并映射为1到2n的整数,同时记录新坐标的每两个相邻坐标之间在离散化前实际的距离是多少。

具体操作如图:

在这里插入图片描述

#include<iostream>
#include<algorithm>
using namespace std;
long long xx[201],yy[201],lrx[101],lry[101],rux[101],ruy[101],x[201],y[201],res;
int n,mm,m1,m2;
bool flag;
long long abss(long long x)
{
    if (x<0) return -x;
    return x;
}
int main()
{
    ios::sync_with_stdio(0);//关闭cin cout 同步
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>lrx[i]>>lry[i]>>rux[i]>>ruy[i];
        xx[++mm]=lrx[i];yy[mm]=lry[i];
        xx[++mm]=rux[i];yy[mm]=ruy[i];
    }
    stable_sort(xx+1,xx+1+mm);
    stable_sort(yy+1,yy+1+mm);//排序
    x[1]=xx[1];y[1]=yy[1];
    m1=1;m2=1;
    for (int i=2;i<=mm;i++)
      if (xx[i]!=xx[i-1])
        x[++m1]=xx[i];//去重
    for (int i=2;i<=mm;i++)
      if (yy[i]!=yy[i-1])
        y[++m2]=yy[i];//去重
    for (int i=1;i<m1;i++)
    {
        for (int j=1;j<m2;j++)
        {
            flag=false;
            for (int k=1;k<=n;k++)
              if (x[i]>=lrx[k]&&x[i+1]<=rux[k]&&y[j]>=lry[k]&&y[j+1]<=ruy[k])//判断是否在改矩形里
              {
                flag=true;
                break;
              }
            if (flag)
            {
                res+=abss((x[i]-x[i+1])*(y[j]-y[j+1]));//计算大小
            }
        }
    }
    cout<<res;
}

使用STL算法离散化来自百科

思路是:先排序,再删除重复元素,最后就是索引元素离散化后对应的值。
假定待离散化的序列为a[n],b[n]是序列a[n]的一个副本,则对应以上三步为:

  sort(sub_a,sub_a+n);
  int size=unique(sub_a,sub_a+n)-sub_a;//size为离散化后元素个数
  for(i=0;i<n;i++)
	  a[i]=lower_bound(sub_a,sub_a+size,a[i])-sub_a + 1;//k为b[i]经离散化后对应的值

对于第3步,若离散化后序列为0,1,2,…,size - 1则用lower_bound,从1,2,3,…,size则用upper_bound。其中lower_bound返回第1个不小于b[i]的值的指针,而upper_bound返回第1个大于b[i]的值的指针,当然在这个题中也可以用lower_bound然后再加1得到与upper_bound相同结果,两者都是针对以排好序列。使用STL离散化大大减少了代码量且结构相当清晰。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值