这道题看似像DP,又像图论,结果二者都不是。
正解居然是————旋转卡壳+最短路?
其实我看了很久都没有看出来,果然我太蒟蒻了。
这个题,只要构建出了图的模型就好做了,我们可以将一个亲戚与他相邻的监视范围有公共边的建一条双向边,边长为1,凡是监视范围与矩阵边界相邻的亲戚就建一条他到终点的边,边长也为1,然后找到最开始监视目标的那个人,以他的编号为起点,跑一边起点到终点的最短路,就是答案了。而最短路很容易用SPFA或者Dijkstra算法就得出答案了。
然而,如何建边呢?如何确定监视范围呢?根据题意,我们可以看出,一个亲戚的监视范围与另一个亲戚的监视范围的分界线就是他们俩的连线的中垂线,自己画画图就知道了其实这个很像泰森多边形(题外话,并无什么用)。然后对于每一个亲戚的监视范围就是他与所有其他亲戚的分界线的面对他自己的半平面交,注意的是还要限制在给定的矩阵内。这样两两枚举,就在的时间复杂度内求出并建出图啦!(具体看代码),然后很小,就可以过了。
本蒟蒻代码丑且常数炒鸡大,将就看吧
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define db double
using namespace std;
const int M=1e4+1;
const int inf=0x7fffffff;
const db eps=1e-10;
int dcmp(db x){if(fabs(x)<eps) return 0;return x<0?-1:1;}
int T,n,s,t;
//s,t表示起点终点编号
db sx,sy,bx,by;
//矩阵大小和起点坐标
struct ss{
int to,last,val;
ss(int a=0,int b=0,int c=1):to(a),last(b),val(c){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b){
g[++cnt]=ss(b,head[a]);head[a]=cnt;
g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
int dis[M];
bool vis[M];
int sze,que[M],pp,qq;
int spfa(int tot){
for(int i=0;i<tot;i++) dis[i]=inf,vis[i]=0;
vis[s]=1;dis[s]=0;que[pp=qq=0]=s;
for(;pp<=qq;pp++){
int now=que[pp%tot];
vis[now]=0;
for(int i=head[now];i;i=g[i].last){
if(dis[g[i].to]>dis[now]+g[i].val){
dis[g[i].to]=dis[now]+g[i].val;
if(!vis[g[i].to]){
que[++qq%tot]=g[i].to;
vis[g[i].to]=1;
if(dis[que[(pp+1)%tot]]>dis[que[qq%tot]])swap(que[(pp+1)%tot],que[qq%n]);
}
}
}
}
return dis[t];
}
//这里我用的SPFA的最短路
struct point{
db x,y;
point(db a=0,db b=0):x(a),y(b){}
void in(){scanf("%lf%lf",&x,&y);}
}po[M];
point operator +(point a,point b){return point(a.x+b.x,a.y+b.y);}
point operator -(point a,point b){return point(a.x-b.x,a.y-b.y);}
point operator *(point a,db b){return point(a.x*b ,a.y*b );}
point operator /(point a,db b){return point(a.x/b ,a.y/b );}
db cross(point a,point b){return a.x*b.y-a.y*b.x;}
db dot (point a,point b){return a.x*b.x+a.y*b.y;}
//计算几何基础啦,就不解释了
point rotate(point a){return point(-a.y,a.x);}
point getmid(point a,point b){return point((a.x+b.x)/2,(a.y+b.y)/2);}
//rotate求逆时针旋转向量90度
//getmid求线段中点,这两个是求中垂线用的
struct line{
point p,v;
db ang;
int id;
line(){}
line(point a,point b,int c):p(a),v(b),id(c){ang=atan2(v.y,v.x);}
bool operator <(line a)const{return ang<a.ang;}
point getp(db t){return p+v*t;}
}l[M];
bool onleft(line le,point p){return cross(le.v,p-le.p)>0;}
point getlinecut(line a,line b){
point u=a.p-b.p;
db t=cross(b.v,u)/cross(a.v,b.v);
return a.getp(t);
}
int fi,la,vnt;
point p[M];
line q[M];
bool no[M];
void init(int a,int tot){
l[0]=line(point(0,sy),point(0,-1),n);
l[1]=line(point(0,0),point(1,0),n);
l[2]=line(point(sx,0),point(0,1),n);
l[3]=line(point(sx,sy),point(-1,0),n);
vnt=4;
for(int i=0;i<tot;i++){
if(i==a||no[i]) continue;
point mid=getmid(po[i],po[a]);
point v=rotate(po[i]-po[a]);
l[vnt++]=line(mid,v,i);
}
//初始化a号亲戚的边界
}
void halfcut(int a,int tot){
sort(l,l+tot);
q[fi=la=0]=l[0];
for(int i=1;i<tot;i++){
while(fi<la&&!onleft(l[i],p[la-1])) --la;
while(fi<la&&!onleft(l[i],p[fi ])) ++fi;
q[++la]=l[i];
if(fabs(cross(q[la].v,q[la-1].v))<eps){
--la;if(onleft(q[la],l[i].p)) q[la]=l[i];
}
if(fi<la) p[la-1]=getlinecut(q[la],q[la-1]);
}
while(fi<la&&!onleft(q[fi],p[la-1])) --la;
if(la-fi<=1) return;
for(int i=fi;i<=la;i++){add(a,q[i].id);}//id记录这条边所对的那个亲戚的编号
}
//半平面交模板
void clear(){
cnt=0;memset(head,0,sizeof(head));
memset(no,0,sizeof(no));
}
//初始化,因为有多组数据
db dist(point a,point b){return dot(a-b,a-b);}
//求两点之间的距离
int main()
{
scanf("%d",&T);
while(T--){
clear();
scanf("%d",&n);
scanf("%lf%lf%lf%lf",&sx,&sy,&bx,&by);
if(!n){printf("0
");continue;}//注意特判
t=n;//中点编号
int pos=0;
db len=1e150;
for(int i=0;i<n;i++){
po[i].in();
if(po[i].x>sx||po[i].y>sy)no[i]=1;
//不知到为啥还有站在矩阵外的亲戚,需要特殊处理剔除掉
db now=dist(po[i],point(bx,by));
if(len>now) len=now,pos=i;
}
s=pos;//距离起点最近的那个亲戚就是最开始看守它的
for(int i=0;i<n;i++){
if(no[i]) continue;
init(i,n);
halfcut(i,vnt);
//建图啦
}
printf("%d
",spfa(n+2));
//最短路出答案
}
return 0;
}
注意一下特判和精度就好啦,最讨厌卡精度的题,不过这道还好