UVA303 Pipe 题解

本文解析了一道关于计算光线穿过管道中特定点的最远距离的IT题目。通过枚举拐点并分析管道的单调性,确定光线应靠近哪一端点以最大化答案。使用向量和三角形面积计算来确定直线与线段的交点,最后处理特殊边界情况。

UVA303 Pipe 题解

这题真被坑惨了。

解法

枚举所有拐点的上下端点(但是显然选同一处拐点的上下端点是没有意义的),然后使我们求的直线穿过这两个点,暴力验证直线是否可以穿过管道或者求最远到达的距离。

为什么选端点可以保证答案最大化?

我们称两个拐点之间的那段管道为一段管道,显然一段管道的上下两边的斜率是相同的,而且是单调的。我们分类讨论。

  • 如果这段管道是单调递减的(斜率为负),显然我们可以让光线尽可能靠近下拐点以增大答案。
  • 如果这段管道是单调递增的(斜率为正),显然我们可以让光线尽可能靠近上拐点以增大答案。

综上,靠近端点能使答案最大化。

接下来就是喜闻乐见的判断直线与线段是否相交以及求交点坐标。

判断线段相交的做法还是用向量判断。计算交点我们详细解释。

为了方便讲解,我们将直线上任取两点和线段两端点这四个点分别记作 A,B,C,DA,B,C,DA,B,C,D

如图,设直线与线段交点为 EEE

图

根据小学四年级(雾)学的燕尾模型,AEEB=SΔADCSΔBDC\dfrac{AE}{EB}=\dfrac{S_{\Delta ADC}}{S_{\Delta BDC}}EBAE=SΔBDCSΔADC,所以 AEAB=SΔADCSΔADC+SΔBDC\dfrac{AE}{AB}=\dfrac{S_{\Delta ADC}}{S_{\Delta ADC}+S_{\Delta BDC}}ABAE=SΔADC+SΔBDCSΔADC,三角形面积可以用叉积轻松求出。

所以直线与线段交点为 (XA+(XB−XA)×AEAB,YA+(YB−YA)×AEAB)(X_A+(X_B-X_A) \times \dfrac{AE}{AB},Y_A+(Y_B-Y_A) \times \dfrac{AE}{AB})(XA+(XBXA)×ABAE,YA+(YBYA)×ABAE)

基本上就做完了,剩下还要特判光线有没有一直在管道外就行了。

代码

#include<bits/stdc++.h>
const double eps=1e-8;
struct point
{
	double x,y;
	point(){x=y=0;}
	point(double x,double y){this->x=x,this->y=y;}
	inline point operator-(const point &rhs) const{return point(x-rhs.x,y-rhs.y);}
	inline double operator*(const point &rhs) const{return x*rhs.y-y*rhs.x;}
	inline bool operator==(const point &rhs) const{return fabs(x-rhs.x)<=eps && fabs(y-rhs.y)<=eps;}
};
inline int sgn(double x){return (fabs(x)<eps)?0:(x>0?1:-1);}
struct seg
{
	point s,t;
	inline seg(){}
	inline seg(point s,point t){this->s=s,this->t=t;}
	inline friend bool cross(const seg &lhs,const seg &rhs) // 第一个参数传直线,第二个参数传线段
	{
		return sgn((rhs.s-lhs.s)*(lhs.t-lhs.s))*sgn((rhs.t-lhs.s)*(lhs.t-lhs.s))<0;
	}
	inline friend point calc(const seg &lhs,const seg &rhs)
	{
		double ix,iy,rat;
		rat=(rhs.t-lhs.s)*(rhs.s-lhs.s)/((rhs.t-lhs.s)*(rhs.s-lhs.s)-(rhs.t-lhs.t)*(rhs.s-lhs.t));
		ix=lhs.s.x+(lhs.t.x-lhs.s.x)*rat,iy=lhs.s.y+(lhs.t.y-lhs.s.y)*rat;
		return point(ix,iy);
	}
};
int n;
point top[30],bot[30];
double ans;
seg lig;
bool flag;
inline void solve()
{
	int fl=0;
	for(int i=1;i<n && !fl;i++)
	{
		if(sgn((lig.t-lig.s)*(top[i]-lig.s))==-1 || sgn((lig.t-lig.s)*(bot[i]-lig.s))==1)
		{
			fl=1;
			break;
		}
		if(cross(lig,seg(top[i],top[i+1])) || calc(lig,seg(top[i],top[i+1]))==top[i] && sgn((lig.t-lig.s)*(top[i+1]-lig.s))==-1)
			ans=std::max(ans,calc(lig,seg(top[i],top[i+1])).x),fl=1;
		if(cross(lig,seg(bot[i],bot[i+1])) || calc(lig,seg(bot[i],bot[i+1]))==bot[i] && sgn((lig.t-lig.s)*(bot[i+1]-lig.s))==1)
			ans=std::max(ans,calc(lig,seg(bot[i],bot[i+1])).x),fl=1;
	}
	if(!fl) flag=1;
}
int main()
{
	while(1)
	{
		scanf("%d",&n),ans=-1e18,flag=0;
		if(n==0) break; 
		for(int i=1;i<=n;i++) scanf("%lf%lf",&top[i].x,&top[i].y),bot[i]=top[i]-point(0,1);
		for(int i=1;i<=n && !flag;i++)
			for(int j=i+1;j<=n && !flag;j++)
			{
				lig=seg(top[i],top[j]),solve();
				lig=seg(top[i],bot[j]),solve();
				lig=seg(bot[i],top[j]),solve();
				lig=seg(bot[i],bot[j]),solve();
			}
		if(flag) printf("Through all the pipe.\n");
		else printf("%.2lf\n",ans+eps);
	}
	return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值