USACO Training 3.4.1 Closed Fences闭合的栅栏 题解与分析

来源:互联网 发布:gps工作原理 知乎 编辑:程序博客网 时间:2024/06/05 07:41

USACO 3.4.1 Closed Fences题解

      首先,很感谢NOCOW中分析的提示以及ID: lorabit1的程序。

一:题意

Closed Fences闭合的栅栏

描述

一个闭合的栅栏是平面上的一些不相交的首尾相连的线段形成的多边形,有N个角(顶点) (3 < N < 200)。 顶点不重合,它以逆时针方式以数组{xi, yi}给出(i=1,2,...,N)。

每一对相邻的顶点都是一条栅栏。因此共有N条栅栏 (定义xN+1=x1, yN+1=y1)。

这里有一个栅栏的例子和一个点x,y:

                        * x3,y3

                x5,y5  / \

   x,y *          *   /   \

                 / \ /     \

                /   *       \

          x6,y6*   x4,y4     \

               |              \

               |               \

          x1,y1*----------------* x2,y2

请编写一个程序实现下面的任务:

  • 检查输入的顶点列表{xi,yi}, i=1,2,...,N, 判断它是否为一个合法的闭合栅栏。
  • 找出所有可以被站在点(x,y)处的人所能看到的栅栏(忽略人的高度),因为有的栅栏会被另外的栅栏所挡住。

只有当存在从(x,y)发射的一条射线第一个穿过栅栏i时,栅栏i是可以被看见的。如果栅栏是平行于目光的,它并不认为是可以看见的。在上面的例子里,线段[x3,y3]-[x4,y4], [x5,y5]-[x6,y6], [x6-y6]-[x1,y1]是可以被(x,y)看见的。格式

PROGRAM NAME: fence4

INPUT FORMAT:

(file fence4.in)

第一行: N, 表示闭合栅栏的顶点数。

第二行: 两个整数x和y,表示观测者的位置。两个整数都是16位的。即2^16,在longlong或longint范围内。

第3到N+2行: 每行一对整数(x,y)表示对应闭合栅栏的第k个顶点的坐标。坐标以逆时针顺序给出。整数绝对值不超过1000。

注意:我添加了该题的新的第12个测试点。如果你认为这个点的数据是错误的,发送邮件到Rob(kolstad@ace.delos.com)在您的邮件主题中一定要包括USACO!

OUTPUT FORMAT:

(file fence4.out)

如果给出的序列不是一个合法的闭合栅栏,那么输出文件只需要输出“NOFENCE”。

栅栏用两端的顶点表示,顶点的输出顺序以输入文件中的顺序为准。把栅栏按照最后一个点在输入文件中的顺序排序。如果两条栅栏的最后一个点是一样的,就以它们第一个点的顺序排序。

SAMPLE INPUT

13

5 5

0 0

7 0

5 2

7 5

5 7

3 5

4 9

1 8

2 5

0 9

-2 7

0 3

-3 1

SAMPLE OUTPUT

7

0 0 7 0

5 2 7 5

7 5 5 7

5 7 3 5

-2 7 0 3

0 0 -3 1

0 3 -3 1

 

二:题解

         首先,输入时可以用结构体存下每个点的横坐标和纵坐标,然后每次枚举一个点i(相当于枚举一条边,因为每次都是用点i和点i+1进行下一步操作),用线段i-i+1<记为边A>进行下一步操作。

          步骤一:检查线段i-watcher及线段i+1-watcher是否被一线段完全覆盖,若是那么就直接退出,返回false<上述操作用一个for循环枚举每条线段来做>.在图一中,线段3-4watcher的连线与其他线段重合了<线段1-6>,直接返回false

          步骤二:若是在步骤一中没有退出,就进行第二步操作:记i点与watcher的连线为Ai+1点与watcher的连线为B,线段i-i+1的中点与watcher的连线为C。首先,枚举每条线段<D>,然后分别判断DADBDC是否相交,然后分别用q1q2q3记录结果<若相交则变为true>q1q2q3的初始值为true。最后判断q1q2q3中是否有一个为true<即有可以看到线段i-i+1的位置>,若是返回true,否则先判断是否超二分层数<小优化>,若没有就对线段i-(i+j)/2(i+j)/2-j进行同样的操作

        →如何判断两线段相交?

                 ---如图2,设标有A的向量为基准向量,那么方法为:

                      ①:从A点向另一向量两端连线(两线代表的向量为RT),用叉乘(A*TA*R)判断是否为一正一副

                         ②:从B点做同样的工作

                         ③:若上述①②全满足,则结论成立

 

        若是在上面的操作中返回了true,就将ii+1点记下来,ans+1,最后按顺序输出即可。

 

三:代码

注:本代码中判断线段重合的方法不完全正确,但由于数据水,可以过全点

/*   ID:csyzcyj1   PROG:fence4   LANG:C++*/#include<stdio.h>#include<string.h>#include<stdlib.h>#include<algorithm>#include<iostream>#include<vector>#include<stack>#include<queue>#include<cmath>using namespace std;#define MAX 201#define eps 1e-7struct point{double x,y;};point a[MAX],watcher,out[MAX][3];int N,ans=0;bool cline(point line1a,point line1b,point line2a,point line2b)//判断相交{      point using1,using2,using3,using4,using5,using6;      using1.x=line2a.x-line1a.x,using1.y=line2a.y-line1a.y;//a-c      using2.x=line1b.x-line1a.x,using2.y=line1b.y-line1a.y;//a-b      using3.x=line2b.x-line1a.x,using3.y=line2b.y-line1a.y;//a-d            using4.x=line1a.x-line2b.x,using4.y=line1a.y-line2b.y;//d-a      using5.x=line1b.x-line2b.x,using5.y=line1b.y-line2b.y;//d-b      using6.x=line2a.x-line2b.x,using6.y=line2a.y-line2b.y;//d-c      if((using1.x*using2.y-using2.x*using1.y)*(using3.x*using2.y-using2.x*using3.y)>0)            return false;      if((using4.x*using6.y-using6.x*using4.y)*(using5.x*using6.y-using6.x*using5.y)>0)            return false;      return true;}bool sort_max(point now1,point now2){      if(fabs(now1.x-now2.x)<eps && fabs(now1.y-now2.y)<eps)            return true;      return false;}bool check(point now1,point now2,int trying){      point using1;      using1.x=(now1.x+now2.x)/2.0;      using1.y=(now1.y+now2.y)/2.0;      for(int i=1;i<=N;i++)//判断该区间的中点和两端与视角连线的相交情况      {            if(i!=trying)            {                  if(cline(now1,watcher,a[i],a[i+1]) && cline(now2,watcher,a[i],a[i+1]))                        return false;//检查now1到watcher的线段及now2到watcher的线段是否被一线段完全覆盖            }      }      bool q1=true,q2=true,q3=true;      for(int i=1;i<=N;i++)      {            if(i!=trying)            {                  if(q1 && cline(now1,watcher,a[i],a[i+1]))   q1=false;//now1到watcher的线段                  if(q2 && cline(now2,watcher,a[i],a[i+1]))   q2=false;//now2到watcher的线段                   if(q3 && cline(using1,watcher,a[i],a[i+1])) q3=false;//now1与now2的中点到watcher的线段                              }      }      if(q1 || q2 || q3)   return true;//如果三线段有一条线段未与其他任意一条线段相交      if(sort_max(now1,now2))   return false;//若now1与now2间的距离小于eps,直接退出       return (check(now1,using1,trying) || check(using1,now2,trying));//在二分的左区间[now1,(now1+now2)/2][(now1+now2)/2,now2]中找       return false;}void figure(){      a[N+1]=a[1];      for(int i=1;i<=N-2;i++)      {            if(check(a[i],a[i+1],i))//从线段a[i]-a[i+1]的区间看             {                  out[++ans][1]=a[i];                  out[ans][2]=a[i+1];            }      }      if(check(a[1],a[N],N))//这两个点的线段终点相同,根据题意,若终点相同,按起点排序       {            out[++ans][1]=a[1];            out[ans][2]=a[N];      }      if(check(a[N-1],a[N],N-1))      {            out[++ans][1]=a[N-1];            out[ans][2]=a[N];      }      }int main(){      freopen("fence4.in","r",stdin);  freopen("fence4.out","w",stdout);      scanf("%d",&N);      scanf("%lf%lf",&watcher.x,&watcher.y);      for(int i=1;i<=N;i++)            scanf("%lf%lf",&a[i].x,&a[i].y);      figure();      if(ans==0)   {printf("NOFENCE");return 0;}      printf("%d\n",ans);      for(int i=1;i<=ans;i++)            printf("%.lf %.lf %.lf %.lf\n",out[i][1].x,out[i][1].y,out[i][2].x,out[i][2].y);     //system("pause");      return 0;}


 

转载注明出处:http://blog.csdn.net/u011400953