湖南省第十届程序设计竞赛第一题:点到圆弧的距离

本文介绍了一种计算平面直角坐标系中任意一点到已知三点确定的圆弧的最短距离的方法,通过解析几何求解圆心及半径,并采用二分法逼近最短距离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分别输入起始点A,经过的点B,和结束点C的圆弧,问点P的位置到这段圆弧的最短距离是多少。

测试数据

样例输入:                    样例输出:

0 0 1 1 2 0 1 -1              Case 1: 1.414

3 4 0 5 -3 4 0 1              Case 2: 4.000

解法:

先用3个方程求出圆的方程求出圆心(X0,Y0)以及圆的半径sqrt(R)。然后不知是三分还是二分。从两端点位置开始枚举寻找。更新圆弧两端点中心位置Mid,如果左边端点到P点距离近就使右边端点更新为Mid,反之亦然。找中心位置Mid的时候,用相似三角形和点绕点旋转90度的方法找到Mid点。特殊情况需要判断点在两端点构成线段的左右位置。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <queue>
#include <map>
#include <stack>
#include <list>
#include <vector>
#include <ctime>
#define LL __int64
#define eps 1e-8
using namespace std;
double X0,X1,X2,X3,px,py,Y0,Y1,Y2,Y3,R;
int flag;
struct point
{
	double x,y;
	point (double x=0,double y=0):x(x),y(y){}
};
int dcmp(double x)
{
	if (fabs(x)<eps) return 0;
	else return x<0?-1:1;
}
double findy(double x)
{
	double ans=0;
	ans=sqrt(R-(x-X0)*(x-X0))+Y0;
	return ans;
}
double dis(double x,double y,double xx,double yy)
{
	return sqrt((xx-x)*(xx-x)+(yy-y)*(yy-y));
}
int sfun(point a,point b,point c)
{
	int ans=(a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x);
	if (ans>0) return 1;
	if (ans<0) return -1;
	if (ans==0) return 0;
}
point go(point v,point l,point r)
{
	if (X0==v.x && Y0==v.y)
	{
		point p1,p2;
		p1=point(l.y-v.y+v.x,l.x-v.x+v.y);
		p2=point(l.y-v.y+v.x,v.x-l.x+v.y);
		if (sfun(l,r,p1)==flag)
			return p1;
		else return p2;
	}
	else
	{
		double gg=dis(v.x,v.y,X0,Y0);
		double gg1=sqrt(R);
		l.x=X0+(v.x-X0)/gg*gg1;
		l.y=Y0+(v.y-Y0)/gg*gg1;
	//	point rr=point(X0,Y0);
	//	if (sfun(rr,l,v)==0) cout<<"%%%"<<endl;
		return l;
	}
}
int main()
{
	int cas=1;
	//freopen("out.txt","w",stdout);
	while (~scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&X1,&Y1,&X2,&Y2,&X3,&Y3,&px,&py))
	{
		double k,k1,g,g1,z,z1,a,b,a1,b1;
		k=2*(X1-X2);k1=2*(X1-X3);
		g=2*(Y1-Y2);g1=2*(Y1-Y3);
		z=Y1*Y1+X1*X1-X2*X2-Y2*Y2;
		z1=Y1*Y1+X1*X1-X3*X3-Y3*Y3;
		a=k/g;b=z/g;
		a1=k1/g1;b1=z1/g1;
		if (k==0)
		{
			Y0=z/g;
			X0=(z1-g1*Y0)/k1;
		}
		else if (k1==0)
		{
			Y0=z1/g1;
			X0=(z-g*Y0)/k;
		}
		else if (g==0)
		{
			X0=z/k;
			Y0=(z1-k1*X0)/g1;
		}
		else if (g1==0)
		{
			X0=z1/k1;
			Y0=(z-k*X0)/g;
		}
		else
		{
			X0=(b1-b)/(a1-a);
			Y0=(z-k*X0)/g;
		}
		R=(X1-X0)*(X1-X0)+(Y1-Y0)*(Y1-Y0);
		point l=point(X1,Y1);
		point r=point(X3,Y3);
		point zh=point(X2,Y2);
		flag=sfun(l,r,zh);
		double ans=0x3fffffff;
		int w1=0,w2=0,w3=0;
		w1=dis(X0,Y0,l.x,l.y);
		w2=dis(X0,Y0,r.x,r.y);
		w3=dis(X0,Y0,zh.x,zh.y);
		double rr=sqrt(R);
	//	cout<<w1<<w2<<w3<<endl;
	//	if (w1-rr<=eps && w2-rr<=eps && w3-rr<=eps) cout<<"YES"<<endl;
		while (dis(l.x,l.y,r.x,r.y)>eps)
		{
			point v=point((l.x+r.x)/2,(l.y+r.y)/2);
			point mid=go(v,l,r);
			double gl=dis(l.x,l.y,px,py);
			double gr=dis(r.x,r.y,px,py);
			if (gl>=gr)
			{
				if (ans-gr>eps)
					ans=gr;
				l.x=mid.x;
				l.y=mid.y;
			}
			else
			{
				if (ans-gl>eps)
					ans=gl;
				r.x=mid.x;
				r.y=mid.y;
			}
		}
		printf("Case %d: %lf\n",cas++,ans);
	}
	return 0;
}

我们其实不需要用精度计算。只需要判断p点到圆心是否在圆的起始点经过中点到终点的角度,如果在角度范围内则最下距离为圆心距离到p点距离减去半径的绝对值,不在则为起始点或终点到p点两者的最小距离。

AC代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <queue>
#include <map>
#include <stack>
#include <list>
#include <vector>
#include <ctime>
#define LL __int64
#define eps 1e-8
using namespace std;
double X0,X1,X2,X3,px,py,Y0,Y1,Y2,Y3,R;
int flag;
struct point
{
	double x,y;
	point (double x=0,double y=0):x(x),y(y){}
};
int dcmp(double x)
{
	if (fabs(x)<eps) return 0;
	else return x<0?-1:1;
}
double findy(double x)
{
	double ans=0;
	ans=sqrt(R-(x-X0)*(x-X0))+Y0;
	return ans;
}
double dis(double x,double y,double xx,double yy)
{
	return sqrt((xx-x)*(xx-x)+(yy-y)*(yy-y));
}
int sfun(point a,point b,point c)
{
	double ans=(a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x);
	if (ans>0) return 1;
	if (ans<0) return -1;
	if (ans==0) return 0;
}
point go(point v,point l,point r)
{
	if (X0==v.x && Y0==v.y)
	{
		point p1,p2;
		p1=point(l.y-v.y+v.x,l.x-v.x+v.y);
		p2=point(l.y-v.y+v.x,v.x-l.x+v.y);
		if (sfun(l,r,p1)==flag)
			return p1;
		else return p2;
	}
	else
	{
		double gg=dis(v.x,v.y,X0,Y0);
		double gg1=sqrt(R);
		l.x=X0+(v.x-X0)/gg*gg1;
		l.y=Y0+(v.y-Y0)/gg*gg1;
	//	point rr=point(X0,Y0);
	//	if (sfun(rr,l,v)==0) cout<<"%%%"<<endl;
		return l;
	}
}
int main()
{
	int cas=1;
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	while (~scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&X1,&Y1,&X2,&Y2,&X3,&Y3,&px,&py))
	{
		double k,k1,g,g1,z,z1,a,b,a1,b1;
		k=2*(X1-X2);k1=2*(X1-X3);
		g=2*(Y1-Y2);g1=2*(Y1-Y3);
		z=Y1*Y1+X1*X1-X2*X2-Y2*Y2;
		z1=Y1*Y1+X1*X1-X3*X3-Y3*Y3;
		a=k/g;b=z/g;
		a1=k1/g1;b1=z1/g1;
		if (k==0)
		{
			Y0=z/g;
			X0=(z1-g1*Y0)/k1;
		}
		else if (k1==0)
		{
			Y0=z1/g1;
			X0=(z-g*Y0)/k;
		}
		else if (g==0)
		{
			X0=z/k;
			Y0=(z1-k1*X0)/g1;
		}
		else if (g1==0)
		{
			X0=z1/k1;
			Y0=(z-k*X0)/g;
		}
		else
		{
			X0=(b1-b)/(a1-a);
			Y0=(z-k*X0)/g;
		}
		R=(X1-X0)*(X1-X0)+(Y1-Y0)*(Y1-Y0);
		point l=point(X1,Y1);
		point r=point(X3,Y3);
		point zh=point(X2,Y2);
		point pp=point(px,py); 
		point o=point(X0,Y0);
		int flag=sfun(zh,r,l);
		double j1=atan2(l.x-o.x,l.y-o.y);
		double j2=atan2(zh.x-o.x,zh.y-o.y);
		double j3=atan2(r.x-o.x,r.y-o.y);
		double j4=atan2(pp.x-o.x,pp.y-o.y);
		double ans1=fabs(sqrt(R)-dis(pp.x,pp.y,o.x,o.y));
		double ans2=min(dis(l.x,l.y,pp.x,pp.y),dis(r.x,r.y,pp.x,pp.y));
		double ans;
		if (flag)
		{
			if (j1<j3)
			{
				if ((j2>j1 && j3>j2)==(j4>j1 && j3>j4)) ans=ans1;
				else ans=ans2;
			}
			else
			{
				if ((j2>j3 && j1>j2)==(j4>j3 && j1>j4)) ans=ans1;
				else ans=ans2;
			}
		}
		else
		{
			if (j1<j3)
			{
				if ((j2>j3 && j1>j2)==(j4>j3 && j1>j4)) ans=ans1;
				else ans=ans2;
			}
			else
			{
				if ((j2>j1 && j3>j2)==(j4>j1 && j3>j4)) ans=ans1;
				else ans=ans2;
			}
		}
		printf("Case %d: %.3lf\n",cas++,ans);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值