中山大学校队选拔赛第一章题1【紧急逃离Emergent escape】----2015年1月26日
一: 题意描述
二:题目分析
本题的大致意思是讲:在给定的一个大圆上挖去很多圆(这些圆有的在大圆里面,有的在大圆外面,有的与圆相加),凡是被圆占据的部分则不能通行。
现在给定两个点,(lifeship和controlling room)如果两者能够到达的话表示能够Escape,否则就只有Die hard。
本题的主要考查图论知识和计算几何方面的知识。首先我们对于这个问题需要建模。我们首先可以把这个大圆看成单独的一个区域。现在的问题就是在整个大圆内找不到一条线可以让lifeship和controlling room 相连。下面我们看看示意图:
现在的任务就是在区域A中找一个与区域A(虚线以左)圆弧相交的圆R1,在区域B中与圆弧相交的圆R2.然后通过其它的圆把这两个圆连接起来,此时则一定不能Escape!。现在的任务就是如何把各个陨石(圆)之间的关系表示出来?我们可以这么做:首先我们通过如下条件进行建模:把每一个陨石看做一个点,不同陨石如果相交则连一条边,因而可以建立一个图。(具体建图规则如下)
(1)如果两个圆一个在A区域而另外一个在B区域,并且两个圆的交点在大圆外,我们不考虑两者的关系(即两个点之间不连边);
(2)如果两个圆之间的交点在大圆内,那么我们可以把两个点之间连一条边);
(3)如果两个圆相离,同样也不连边。
通过这样我们便可以建立一个图。剩下的工作就是看能否找到一条从A区域到B区域的连线。下面的工作我们可以采取DFS的方法进行:
(1)首先对各个点进行初始化(利用数组b[]打标记:
(1.1)如果在大圆外那么我们标记b[i]=0;
(1.2)如果陨石完全被包含在大圆内,我们打上标记b[i]=3;
(1.3)如果陨石与大圆相交在区域A内,我们打上标记b[i]=1;
(1.4)如果陨石和大圆相交在区域B中,我们打上标记b[i]=2;
(2)下面的工作就是建图,规则如上介绍的进行。
(3)首先找到一个在区域A的点,接下来找与A相邻的点,进一步DFS直到找到在区域B中的点为止结束递归。整个过程可以设置一个标记ok,并初始化为1.
注意在DFS过程中我们需要学会剪枝。我在代码里面会就如何剪枝给出证明过程。
三 :AC代码
#include<iostream> #include<cmath> #include<string.h> using namespace std; const int maxn=1000+10; const double eps=1e-8; const double pi=acos(-1.0); double d1,d2,R; double xd1,yd1,xd2,yd2; int n; double x[maxn],y[maxn],r[maxn]; int a[maxn][maxn]; int b[maxn]; int cas; double dis(double x,double y) { return sqrt(x*x+y*y); } double helen(double a ,double b,double c)//海伦公式需要牢记 { double p=(a+b+c)/2.0; return sqrt(p*(p-a)*(p-b)*(p-c)); } int intersect(int i,int j )//这是判断两圆是否相交的函数,注意为何可以这样做? { double di=dis(x[i],y[i]); double dj=dis(x[j],y[j]); double ij=dis(x[i]-x[j],y[i]-y[j]); double S=helen(di,dj,ij); double Si=helen(di,r[i],R); double Sj=helen(dj,r[j],R); double Sij=helen(ij,r[j],r[i]); return Si+Sj+Sij>S; } int dfs(int root) { if(b[root]==2) return 0; b[root]=0;//这里需要好好理解为何要这样处理。相当于剪枝 int i; for(int i=0;i<n;i++) if(b[i]&&a[root][i]==cas) { if(dfs(i)==0) return 0;//说明找到了 } return 1; } int main() { int T; cin>>T; int ok; int i,j; memset(a,0,sizeof(a)); cas=0; while(T--) { cas++; cin>>R>>d1>>d2; if(d1>d2) { swap(d1,d2); } d1=pi/180.0*d1; d2=pi/180.0*d2; xd1=R*cos(d1);//这几个函数是如何在圆内利用弧度求坐标!!! yd1=R*sin(d2); xd2=R*cos(d2); yd2=R*sin(d2); cin>>n; for( int i=0;i<n;i++) cin>>x[i]>>y[i]>>r[i]; ok=1; memset(b,0,sizeof(b)); for(int i=0;i<n;i++) { int d=dis(x[i],y[i]); if(d>R+eps+r[i])//整个陨石在大圆外 { b[i]=0; continue; } if(r[i]+d+eps<R)//整个陨石在大圆内 { b[i]=3; continue; } if(dis(x[i]-xd1,y[i]-yd1)<=eps+r[i]||dis(x[i]-xd2,y[i]-yd2)<=eps+r[i]) { ok=0;//陨石直接会把lifeship或者controlling room 直接摧毁,结束 continue; } int di=acos(x[i]/d); if(y[i]<0.0) di=pi+pi-di; if(di>d1&&di<d2) b[i]=1;//判断是在A区域还是在B区域 else b[i]=2; } if(!ok) { cout<<"Die hard"<<endl; continue; } for(int i =0;i<n;i++) if(b[i]) for(int j =i+1;j<n;j++) if(b[j]) { if(dis(x[i]-x[j],y[i]-y[j])<=r[i]+r[j]) { if(b[i]+b[j]!=3||intersect(i,j))//这里的b[i]+b[j]!=3表示分别在A区域和B区域但是交点在大圆外 a[i][j]=a[j][i]=cas; } } for(int i=0;i<n;i++) { if(b[i]==1) ok=dfs(i); if(!ok) break; } if(ok) cout<<"Escape"<<endl; else cout<<"Die hard"<<endl; } return 0; }
下面我给出DFS中在可以把b[root]置为0的证明过程:
情况1:
假设我们首先找到A区域的点C,root点如图所示,如果从root点不能到达B区域的E,那么我们可以确定得出点D从root点照样不能到达B区域的点E,所以当判断不成立的就可以设置为0达到剪枝的作用。
情况2:
我们此时可以得知如果递归过程中从C经root不能达到B区域,那么我们可以确定从D经root照样不能到达B区域,此时照样可以剪枝。
四:本题总结
(1)本题建模思想需要好好体会;
(2)本题在计算几何里面涉及到数据处理问题,需要特别注意!
(3)本题在弧度和角度转化,弧度求坐标,海伦公式等数学知识涉及很多,需要好好消化;
(4)本题在DFS时涉及到剪枝问题,以后自己在写DFS也要学会模仿(注意严格证明)。