把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ3199】[SDOI2013] escape(半平面交+BFS)

点此看题面

大致题意: 一个矩形内有\(n\)个关键点,每个点被离自己最近的关键点(可能有多个)管辖,问你从给定的起点出发,至少经过几个关键点管辖的区域,才能走出矩形。

前言

这道题其实思维难度不大,至少我也能口胡出大致解法。

然而,这毕竟是一道毒瘤计算几何。。。

先是写了一个自认为比较容易点的写法,结果半个小时之后发现这种写法漏洞百出,只好弃掉。

然后开始老老实实、痛苦地按常规方式去写,真的无数次心态爆炸想放弃,但最后还是咬牙坚持了下来。

写完之后,一切才刚刚开始。由于太久没写计算几何了,代码里真是写了无数个\(bug\)(尤其是求出直线这个地方调了十几分钟真是让我无语。。。),一次又一次在绝望的边缘摸索挣扎。

最终,总算调过了样例,而接下来一发过,不用再去调什么玄学东西,这一点还是挺舒服的。

说实话,这么不容易地做出了一道题,还是挺有成就感的吧。

半平面交

我们考虑如何确定一个关键点的管辖范围。

显然,对于每两个点,它们管辖范围的分界就是二点连线的中垂线。

因此,一个点的管辖范围就是它和所有点连线的中垂线的半平面交。

注意这里中垂线的表示,如果像我一样用两点坐标来表示一条线段的话,这里可能会有些细节,具体实现详见代码。

\(BFS\)

求出了半平面交,实际上我们就求出了与一个点管辖范围相邻的所有点(只要给每条直线记一下是和哪个关键点的中垂线即可)。

显然有一个基本结论:一个点的管辖范围必然只会经过一次。(我觉得这应该不需要解释吧。。。)

因此,我们可以在管辖范围相邻的两点之间连一条边,并在位于矩形周边(也就是管辖范围与外界相邻)的点与我们自己建立的表示外界的点之间连一条边。

最终,我们从管辖起点的关键点出发,求出到外界的最短路,即为答案。而由于此处边权都是\(1\),直接\(BFS\)即可。

至于如何求出管辖起点的关键点,我们只要判断每个关键点管辖的凸包是否包含起点。我的做法是从起点向上引一条线,如果经过奇数条边(实际上此处也就是经过一条边),就说明被包含。

这题的做法其实是很容易理解的,但难就难在代码实现啊。。。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 600
#define DB double
#define INF 1e18
#define eps 1e-8
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,x,y,X,Y,a[N+5],b[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N*N+5];
namespace ComputationGeometry//毒瘤的计算几何
{
	#define Sign(x) (fabs(x)<eps?0:((x)>0?1:-1))
	struct P
	{
		DB x,y;I P(Con DB& a=0,Con DB& b=0):x(a),y(b){}
		I P operator + (Con P& o) Con {return P(x+o.x,y+o.y);}
		I P operator - (Con P& o) Con {return P(x-o.x,y-o.y);}
		I P operator * (Con DB& v) Con {return P(x*v,y*v);}
		I P operator / (Con DB& v) Con {return P(x/v,y/v);}
		I DB operator ^ (Con P& o) Con {return x*o.y-y*o.x;}//叉积
	};
	struct L
	{
		#define Ang(p) (atan2((p).y,(p).x))
		int id;P A,B;DB d;I L(CI p=0,Con P& x=0,Con P& y=0):id(p),A(x),B(y),d(Ang(B-A)){}
		I bool operator < (Con L& o) Con {return Sign(d-o.d)?d<o.d:!~Sign((B-A)^(o.B-A));}//极角排序
	}s[N+5];int m;
	struct G
	{
		int n;P p[N+5];I G(CI x=0):n(x){}I P& operator [] (CI x) {return p[x];}
		I bool Include(Con P& x)//判断是否包含某个点(此题中指起点)
		{
			RI i,t=0;DB k;for(p[n+1]=p[i=1];i<=n;++i)//枚举每一条边
			{
				if(p[i].x==p[i+1].x) continue;
				if(x.x<min(p[i].x,p[i+1].x)) continue;if(x.x>max(p[i].x,p[i+1].x)) continue;
				k=(p[i+1].y-p[i].y)/(p[i+1].x-p[i].x),t^=k*x.x+(p[i].y-k*p[i].x)>x.y;//向上引一条线,判断是否经过
			}return t;
		}
	};
	P p[N+5];L q[N+5];G g;I G HalfPlane(CI x)//半平面交
	{
		#define IP(P,Q) (P.A+(P.B-P.A)*((Q.B-Q.A)^(P.A-Q.A))/((P.B-P.A)^(Q.B-Q.A)))//交点
		#define R(p,s) (!~Sign((s.B-s.A)^(p-s.A)))//判断点是否在直线右边
		RI i,H=1,T=1;for(sort(s+1,s+m+1),q[1]=s[1],i=2;i<=m;++i)
		{
			if(!Sign(s[i].d-s[i-1].d)) continue;
			W(H<T&&R(p[T-1],s[i])) --T;W(H<T&&R(p[H],s[i])) ++H;
			q[++T]=s[i],p[T-1]=IP(q[T-1],q[T]);
		}
		W(H<T&&R(p[T-1],q[H])) --T;W(H<T&&R(p[H],q[T])) ++H;if(T-H<=1) return G(0);//如果只有一个点或是一条边,返回空
		for(i=H;i<=T;++i) add(x,q[i].id);//连边
		for(p[T]=IP(q[T],q[H]),g=G(T-H+1),i=H;i<=T;++i) g[i-H+1]=p[i];return g;//返回凸包
	}
}using namespace ComputationGeometry;
namespace BreadthFirstSearch
{
	int q[N+5],vis[N+5],dis[N+5];
	I int BFS(CI S)//BFS求最短路
	{
		RI i,k,H=1,T=1;memset(vis,0,sizeof(vis)),vis[q[1]=S]=1,dis[S]=0;
		W(H<=T) for(i=lnk[k=q[H++]];i;i=e[i].nxt)
			!vis[e[i].to]&&(dis[e[i].to]=dis[k]+1,vis[q[++T]=e[i].to]=1);
		return dis[n+1];
	}
}using namespace BreadthFirstSearch;
int main()
{
	RI Tt,i,j,S;DB k,t;P A,B;scanf("%d",&Tt);W(Tt--)
	{
		if(scanf("%d%d%d%d%d",&n,&X,&Y,&x,&y),!n) {puts("0");continue;}//特判0
		for(ee=lnk[n+1]=0,i=1;i<=n;++i) scanf("%d%d",a+i,b+i),lnk[i]=0;
		for(i=1;i<=n;++i)//枚举点
		{
			m=4,s[1]=L(n+1,P(0,0),P(X,0)),s[2]=L(n+1,P(X,0),P(X,Y)),//加入四条边界
			s[3]=L(n+1,P(X,Y),P(0,Y)),s[4]=L(n+1,P(0,Y),P(0,0));
			for(j=1;j<=n;++j) if(i^j)//枚举点求中垂线
			{
				//注意半平面交默认左边为保留区间,所以我们要让当前点处于左边,因此中垂线选择的点的坐标比较奇怪,可以画图理解
				if(a[i]==a[j]) {s[++m]=L(j,P(b[j],(b[i]+b[j])/2.0),P(b[i],(b[i]+b[j])/2.0));continue;}//特殊处理横坐标相等
				if(b[i]==b[j]) {s[++m]=L(j,P((a[i]+a[j])/2.0,a[i]),P((a[i]+a[j])/2.0,a[j]));continue;}//特殊处理纵坐标相等
				k=-1.0*(a[i]-a[j])/(b[i]-b[j]),t=(b[i]+b[j])/2.0-k*(a[i]+a[j])/2.0;//求出解析式
				A=P(a[i],k*a[i]+t),B=P(a[j],k*a[j]+t),s[++m]=~Sign(k)?L(j,A,B):L(j,B,A);
			}HalfPlane(i).Include(P(x,y))&&(S=i);//半平面交,同时记录起点
		}printf("%d\n",BFS(S));
	}return 0;
}
posted @ 2020-05-25 13:28  TheLostWeak  阅读(128)  评论(0编辑  收藏  举报