[JZOJ3297] 【SDOI2013】逃考

题目

我发现我现在连题面都懒得复制粘贴了……

题目大意

在一个矩形中有一堆点,这堆点按照以下规则将矩形瓜分成一堆块:
对于每个坐标,它属于离它最近的点的块。
一个人从某个坐标出发到矩形外面,求经过的最少的区域个数。


思考历程

显然,如果将每个点管辖的区域处理出来,和周围接壤的区域连一条边,答案就是一个最短路的事情。
可问题是,怎么求啊?!
作为计算几何白痴,我不得不弃疗……


正解

正解是求半平面交……
显然,对于每个点,枚举其它点,作两点连线的垂直平分线,围成的区域就是它管辖的范围。
所以这就是半平面交的裸题……
首先要有一堆计算几何的知识,向量的基本操作(点积、叉积)、求交点、点到直线距离……
先把这些打一遍。
然后我们求出每条直线的解析式(当然是向量表示),按照极角排序(就是直线和xx轴的夹角),如果两条直线的极角一样就选离点更近的那一条。
接着用一个双向队列,有直线加进来时,看看队尾和队尾的前一条直线的交点是否在这条直线的外面,如果是,就将队尾踢掉。循环操作。
同时队头也要类似地操作。
在搞完之后试着用队头弹掉不合法的队尾。
于是半平面交就处理出来了……
将残留的直线的另一边的区域和自己连边,然后跑最短路。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 610
#define PI 3.1415926535
int n;
double X,Y;
struct DOT{
	double x,y;
} d[N],beg;
int nl;
struct Line{
	DOT p,v;
} l[N];
inline DOT operator+(const DOT &a,const DOT &b)
{return {a.x+b.x,a.y+b.y};}
inline DOT operator-(const DOT &a,const DOT &b)
{return {a.x-b.x,a.y-b.y};}
inline DOT operator*(const DOT &a,const double b)
{return {a.x*b,a.y*b};}
inline double dot(const DOT &a,const DOT &b)
{return a.x*b.x+a.y*b.y;}
inline double cro(const DOT &a,const DOT &b)
{return a.x*b.y-a.y*b.x;}
inline double len(const DOT &a)
{return sqrt(dot(a,a));}
inline double len2(const DOT &a)
{return dot(a,a);}
inline bool direct(const DOT &a,const DOT &b)
{return cro(a,b)<=0;}
inline DOT intersect(const Line &a,const Line &b)
{return b.p+b.v*(cro(a.v,a.p-b.p)/cro(a.v,b.v));}
inline double distl(const Line &a,const DOT b)
{return abs(cro(b-a.p,a.v)/len(a.v));}
int with[N];
double theta[N];
double LEN2[N];
int p[N];
inline bool cmpp(int a,int b)
{return theta[a]<theta[b] || theta[a]==theta[b] && LEN2[a]<LEN2[b];}
int q[N];
struct EDGE{
	int to;
	EDGE *las;
} e[N*N];
int ne;
EDGE *last[N];
inline void link(int u,int v){
	e[++ne]={v,last[u]};
	last[u]=e+ne;
}
int dis[N];
inline void BFS(int S){
	int head=0,tail=1;
	q[1]=S;
	memset(dis,0,sizeof dis);
	dis[S]=1;
	do{
		int x=q[++head];
		for (EDGE *ei=last[x];ei;ei=ei->las)
			if (!dis[ei->to]){
				dis[ei->to]=dis[x]+1;
				if (ei->to==0){
					printf("%d
",dis[ei->to]-1);
					return;
				}
				q[++tail]=ei->to;
			}
	}
	while (head!=tail);
}
int main(){
	freopen("in.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%d%lf%lf%lf%lf",&n,&X,&Y,&beg.x,&beg.y);
		if (n==0){
			printf("0
");
			continue;
		}
		for (int i=1;i<=n;++i)
			scanf("%lf%lf",&d[i].x,&d[i].y);
		ne=0;
		memset(last,0,sizeof last);
		for (int i=1;i<=n;++i){
			l[1]={X,0,0,1};
			l[2]={X,Y,-1,0};
			l[3]={0,Y,0,-1};
			l[4]={0,0,1,0};
			with[1]=with[2]=with[3]=with[4]=0;
			nl=4;
			for (int j=1;j<=n;++j)
				if (i!=j){
					DOT mid={(d[i].x+d[j].x)/2,(d[i].y+d[j].y)/2},t=mid-d[i];
					l[++nl]={mid,-t.y,t.x};
					with[nl]=j;
				}
			for (int j=1;j<=nl;++j){
				theta[j]=atan2(l[j].v.y,l[j].v.x);
				if (theta[j]<0)
					theta[j]+=PI*2;
				LEN2[j]=distl(l[j],d[i]);
				p[j]=j;
			}
			sort(p+1,p+nl+1,cmpp);
			int tmp=nl;
			nl=0;
			for (int j=1;j<=tmp;++j)
				if (j==1 || theta[p[j]]!=theta[p[j-1]])
					p[++nl]=p[j];
			int head=1,tail=0;
			for (int j=1;j<=nl;++j){
				while (head<tail && direct(l[p[j]].v,intersect(l[q[tail-1]],l[q[tail]])-l[p[j]].p)==1)
					--tail;
				while (head<tail && direct(l[p[j]].v,intersect(l[q[head]],l[q[head+1]])-l[p[j]].p)==1)
					++head;
				q[++tail]=p[j];
			}
			while (head<tail && direct(l[q[head]].v,intersect(l[q[tail-1]],l[q[tail]])-l[q[head]].p)==1)
				--tail;
			for (int j=head;j<=tail;++j)
				link(i,with[q[j]]);
		}
		int mni=1;
		for (int i=2;i<=n;++i)
			if (len2(d[i]-beg)<len2(d[mni]-beg))
				mni=i;
		BFS(mni);
	}
	return 0;
}

总结

必须要恶补计算几何
在打计算几何之前,先把一堆模板打下来,这样会方便很多。

原文地址:https://www.cnblogs.com/jz-597/p/11145214.html