中山大学校队选拔赛第一章题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也要学会模仿(注意严格证明)。

原文地址:https://www.cnblogs.com/khbcsu/p/4251323.html