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

  • 相关阅读:
    动态表单实现客户端二次过滤及字段汇总统计
    开放一些常见功能的工具类代码
    动态表单
    客户中增加按钮提前判断是否撞单 并提示
    通过插件来对打印数据进行处理
    mac 升级10.12 php debug 环境 跑不起的解决 解决方案
    感觉世界变化太快...
    Mac 升级一次,php 就崩溃一次,有味,苹果....
    http://s22.app1105796624.qqopenapp.com/
    unity 2d 游戏优化之路 遇坑记录
  • 原文地址:https://www.cnblogs.com/khbcsu/p/4251323.html
Copyright © 2011-2022 走看看