[ABC286Ex] Don't Swim 题解

我们首先求出线段与多边形的交点,如果交点个数 \(<2\) 或者有无数个交点,则可以直接输出 \(S\)\(T\) 之间的距离。

接下来我们考虑交点个数为 \(2\) 的情况。

为了方便,我们记距离 \(S\) 最近的那个交点为 \(P_1\),远的为 \(P_2\)

举个例子:

在这个例子中,直线 \(ST\) 将整个多边形分成了两个凸壳,我们可以从多边形下面过去也可以从上面过去。考虑对于两部份分别求出一条最短路径再取一个最小值。(这里上半部分指的是向量 \(\overrightarrow{ST}\) 的左半平面)

我们先考虑上半部分的一条路径 \(S\rightarrow P_1 \rightarrow A_1\rightarrow A_6 \rightarrow A_5 \rightarrow P_2 \rightarrow T\),可以发现这条路径的一个前缀 \(S\rightarrow P_1 \rightarrow A_1\) 没有 \(S\rightarrow A_1\) 更优(三角形的性质)。

考虑按到 \(S\) 的距离的顺序枚举上半部分的所有点。我们维护一个栈,每次新加入一个点时,如果栈的大小 \(<2\),就直接入栈。

对于其它情况,我们记当前这个点为 \(P\),栈顶的两个点分别为 \(A,B\)\(A\)\(B\) 先入栈),考虑向量 \(\overrightarrow{AB}\) 与向量 \(\overrightarrow{BP}\) 的叉积,如果符号不为则将 \(B\) 弹出栈,重复这个操作直到符号不为负或者栈的大小 \(<2\)。最后再将 \(P\) 入栈。

那么路径的长度就是最后的栈中相邻两个点的距离的和。对于下半部分也是类似的操作,但是在判断叉积的符号时如果符号不为才将 \(B\) 弹出栈。

那么剩下的问题就是如何求出两半部份的点了。我们记 \(P_1\) 所在的线段的两个端点分别是 \(A_i,A_{(i+1)\mod n}\)(如果存在多条线段则任取一个),\(P_2\) 所在的线段的两个端点分别是 \(A_j,A_{(j+1)\mod n}\),那么点 \(A_{i+1},A_{i+2},\dots,A_{n-1},A_0,A_1,\dots A_j\) 属于下半部分,剩下的就属于上半部分。

代码:

#include<bits/stdc++.h>
#define ll long long
#define mxn 100003
#define md 1000000007
#define pb push_back
#define mkp make_pair
#define ld long double
#define umap unordered_map
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
#define pq priority_queue
using namespace std;
struct point{
	ld x,y;
	inline void in(){
		scanf("%Lf%Lf",&x,&y);
	}
}s,t,a[mxn],q[mxn+10];
struct line{
	ld k,b;
	line(point x,point y){
		k=(y.y-x.y)/(y.x-x.x);
		b=x.y-x.x*k;
	}
	inline ld at(ld x){
		return x*k+b;
	}
};
const ld esp=1e-8;
inline point operator-(point x,point y){
	return {x.x-y.x,x.y-y.y};
}
inline bool operator==(point x,point y){
	return fabs(x.x-y.x)<esp&&fabs(x.y-y.y)<esp;
}
inline bool operator==(line x,line y){
	return fabs(x.k-y.k)<esp&&fabs(x.b-y.b)<esp;
}
inline point operator&(line a,line b){
	ld x=(b.b-a.b)/(a.k-b.k);
	return {x,a.k*x+a.b};
}
inline ld operator*(point x,point y){
	return x.x*y.y-x.y*y.x;
}
inline int cmp(ld x){
	if(fabs(x)<esp)return 0;
	return x<0?-1:1;
}
inline ld len(point x){
	return sqrt(x.x*x.x+x.y*x.y);
}
int n,m;
vector<pair<point,int> >st,d1;
ld ans1,ans2;
inline void add1(point x){
	while(m>1&&(x==q[m]||cmp((q[m]-q[m-1])*(x-q[m]))!=1))m--;
	q[++m]=x;
}
inline void add2(point x){
	while(m>1&&(x==q[m]||cmp((q[m]-q[m-1])*(x-q[m]))!=-1))m--;
	q[++m]=x;
}
signed main(){
	scanf("%d",&n);
	rept(i,0,n)a[i].in();
	s.in(),t.in();
	line d(s,t);
	rept(i,0,n){
		line x(a[i],a[(i+1)%n]);
		if(s.x==t.x&&a[i].x==a[(i+1)%n].x){
			if(s.x==a[i].x){
				printf("%.10Lf",len(s-t));
				return 0;
			}
			continue;
		}
		if(s.x!=t.x&&x.k==d.k){
			if(x.b==d.b){
				printf("%.10Lf",len(s-t));
				return 0;
			}
			continue;
		}
		point p;
		if(s.x==t.x){
			p={s.x,x.at(s.x)};
		}else if(a[i].x==a[(i+1)%n].x){
			p={a[i].x,d.at(a[i].x)};
		}else p=x&d;
		if(p.x>=min(a[i].x,a[(i+1)%n].x)&&p.x<=max(a[i].x,a[(i+1)%n].x)&&p.x>=min(s.x,t.x)&&p.x<=max(s.x,t.x)&&p.y>=min(a[i].y,a[(i+1)%n].y)&&p.y<=max(a[i].y,a[(i+1)%n].y)&&p.y>=min(s.y,t.y)&&p.y<=max(s.y,t.y)){
			d1.pb(mkp(p,i));
		}
	}
	for(auto i:d1){
		for(auto j:st)if(i.first==j.first)goto next;
		st.pb(i);
		next:;
	}
	if(st.size()!=2){
		printf("%.10Lf",len(s-t));
		return 0;
	}
	if(len(s-st[0].first)>len(s-st[1].first))swap(st[0],st[1]);
	add1(s),add1(st[0].first);
	int i=(st[0].second+1)%n;
	while(1){
		add1(a[i]);
		if(i==st[1].second)break;
		i=(i+1)%n;
	}
	add1(st[1].first),add1(t);
	rep(i,2,m)ans1+=len(q[i]-q[i-1]);
	m=0;
	add2(s),add2(st[0].first);
	i=st[0].second;
	while(1){
		add2(a[i]);
		if(i==(st[1].second+1)%n)break;
		i=(i+n-1)%n;
	}
	add2(st[1].first),add2(t);
	rep(i,2,m)ans2+=len(q[i]-q[i-1]);
	printf("%.10Lf",min(ans1,ans2));
	return 0;
}
posted @ 2023-01-23 17:32  zifanwang  阅读(6)  评论(0编辑  收藏  举报  来源