Bridge Across Islands----POJ_3608----求两凸包最近距离----旋转卡壳

题目地址:http://poj.org/problem?id=3608

题目的意思已经说的很清楚了,就是要你求出给出的两个凸包的最短距离,但是这两个凸包的点给的并不规律,所以你得自己重新建立凸包。这题一拿着一点思路都没有,查了一下午的资料,看了一些大牛的博客,终于搞清楚了。用所谓的旋转卡壳法。很美妙的思路。

别人已经写得十分的好了,所以我根本没必要自己再转述一遍。

理论请移步:http://blog.csdn.net/dr5459/article/details/7802055

但是有几点要注意:

1、凸包要建好,这是一切的基础。

2、凸包最好是用犄角排序,不知道为什么我用别的排序是错的。

3、要考虑到两个凸包最近的三种情况,所以要考虑仔细,这一点在我的代码里面体现的很明显。

4、自己好烂,要多加学习。

下面上代码:

 
#include<iostream>
#include<algorithm>
#include<cmath> 
using namespace std;

#define eps 1e-8
#define MAXN 10010

double min(double a,double b)
{
	return a<b?a:b;
}
struct point
{
	double x,y; 
}; 
struct convex
{
	point p[MAXN];
	int num_of_points; 
};

 
convex c1;
convex c2;
point p[MAXN]; 

inline double dis_points(point a,point b)
{
	return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)); 
} 

inline double multiply(point p0,point p1,point p2)
{
	return (p1.x - p0.x)*(p2.y - p1.y) - (p2.x - p1.x)*(p1.y - p0.y); 
} 

inline double fabss(double x)
{
	return x>0?x:-x; 
} 

bool cmp1(point a,point b)  //进行犄角排序 
{
	double m = multiply(p[0],a,b);
	if(m > eps)
		return true;
	if(fabss(m) <= eps)
		return dis_points(p[0],a) < dis_points(p[0],b);
	return false;
}

inline void init_convex(point from[],convex &to,int num)
{
	int u=0;
	int top = 0;
	int i;
	for(i=1;i<num;i++)
	{
		if(from[u].y > from[i].y || (from[u].y == from[i].y && from[u].x > from[i].x))
			u = i;
	}
	//cout<<"num   "<<num<<endl;
	swap(from[u],from[0]);
	sort(from+1,from+num,cmp1);
	for(i=0;i<=1;i++)
		to.p[i] = from[i];
	top = 1;
	for(i=2;i<num;i++)
	{
		while(top>=0 && multiply(to.p[top-1],to.p[top],from[i]) < 0)
			top --; 
		to.p[++top] = from[i]; 
	}
	top++;
	to.p[top] = from[num-2];
	
	for(i=num-3;i>=0;i--)
	{
		while(top>=0 && multiply(to.p[top-1],to.p[top],from[i]) < 0)
			top --; 
		to.p[++top] = from[i];
	}
	//cout<<"top "<<top<<endl;
	to.num_of_points = top; 
} 


//点积公式,a为起点 
inline double dot_time(point a,point b,point c)
{
	return (b.x - a.x)*(c.x-a.x) + (b.y-a.y)*(c.y-a.y); 
} 

inline double dis_point_to_line(point a,point b,point c)
{
	//这里巧妙的利用了叉积公式,用面积来转化距离 
	return fabs(multiply(c,a,b) / dis_points(a,b)); 
} 

//点c与线段a,b的距离 
inline double dis_point_to_seg(point a,point b,point c)  
{
	double ans;
	//如果以点c做直线a,b的垂线不能与线段a,b相交,则只能取
	//ac,bc最短的,否则就是c到线段a,b的距离
	//我们用点积来判断以c做垂线到ab能否相交 
	if(dot_time(a,b,c) * dot_time(b,a,c) < 0)
	{
		ans = min(dis_points(a,c), dis_points(b,c)); 
	}
	else
		//求点c到直线ab的距离 
		ans = dis_point_to_line(a,b,c);  
	return ans; 
} 


inline double min_dis_convexs(convex c1,convex c2)
{
	double ans;
	int n1 = 0,n2 = 0;
	int i;
	//找出两个凸包中最低的点和最高的点
	//在第一个凸包里面找最低的,在第二个里面找最高的 
	for(i=1;i<c1.num_of_points;i++)
	{
		if(c1.p[n1].y > c1.p[i].y)
			n1 = i; 
	}
	for(i=1;i<c2.num_of_points;i++)
	{
		if(c2.p[n2].y < c2.p[i].y)
			n2 = i; 
	} 

	int t1 = n1,t2 = n2;
	bool f1 = true, f2 = true;//表示两个凸包旋转是否转完
	//true则表示还没有转完 
	point a,b,o;
	ans = dis_points(c1.p[n1],c2.p[n2]);  //先求出两个
	//最初的点的距离,然后在后面进行更新
	do
	{
		//构造向量a 
		a = c1.p[(n1+1)%c1.num_of_points];
		a.x -= c1.p[n1].x;
		a.y -= c1.p[n1].y;
		//构造向量b
		b = c2.p[n2];
		b.x -= c2.p[(n2 + 1)% c2.num_of_points].x;
		b.y -= c2.p[(n2 + 1)% c2.num_of_points].y;
		
		o.x = 0;
		o.y = 0;
		
		double rot = multiply(o,a,b);
		if(rot < 0)  //转n2最快达到平行 
		{
			ans = min(ans,dis_point_to_seg(c2.p[n2],c2.p[(n2+1)%c2.num_of_points],c1.p[n1]));
			n2 = (n2+1)%c2.num_of_points;
			if(n2 == t2)
				f2 = false;
			
		}
		else if(rot > 0)//转n1最快达到平行 
		{
			ans = min(ans,dis_point_to_seg(c1.p[n1],c1.p[(n1+1)%c1.num_of_points],c2.p[n2]));
			n1 = (n1+1)%c1.num_of_points;
			if(n1 == t1)
				f1 = false; 
			
		}
		else //如果他们同时转达到平行
		//最短距离可能就会出现在四种情况,详见代码 
		{
			ans = min(ans,dis_point_to_seg(c1.p[n1],c1.p[(n1+1)%c1.num_of_points],c2.p[n2]));
			ans = min(ans,dis_point_to_seg(c1.p[n1],c1.p[(n1+1)%c1.num_of_points],c2.p[(n2+1)%c2.num_of_points]));
			ans = min(ans,dis_point_to_seg(c2.p[n2],c2.p[(n2+1)%c2.num_of_points],c1.p[n1]));
			ans = min(ans,dis_point_to_seg(c2.p[n2],c2.p[(n2+1)%c2.num_of_points],c1.p[(n1+1)%c1.num_of_points]));
			n1 = (n1+1)%c1.num_of_points;
			n2 = (n2+1)%c2.num_of_points;
			
			if(n1 == t1)
				f1 = false;
			if(n2 == t2)
				f2 = false; 
		} 
		 
	}while(f1 || f2); 
	
	return ans; 
} 


int main()
{
	int n1,n2; 
	while(scanf("%d%d",&n1,&n2) != EOF && (n1 || n2))
	{
		int i; 
		for(i=0;i<n1;i++)
			scanf("%lf%lf",&p[i].x,&p[i].y);
		init_convex(p,c1,n1);
		
		//for(i=0;i<c1.num_of_points;i++)
			//cout<<"c1 "<<c1.p[i].x<<c1.p[i].y<<endl; 
		for(i=0;i<n2;i++)
			scanf("%lf%lf",&p[i].x,&p[i].y);
		init_convex(p,c2,n2);
		double ans = min_dis_convexs(c1,c2);
		printf("%.5lf\n",ans+eps); 
	}
	return 0;
} 




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值