UVA1318题解

题意简述

给出 $n$ 条不存在三线共点的线段,判断一个原始位置在坐标轴原点的动点能否在不穿过任何一条线段的情况下移动到无穷远处。

题目分析

由于题目给出的是平面直线图,那么有一个直截了当非常容易想到的思路就是找出所有线段围成的区域,再判断动点原位置所在区域是否可以延伸到无穷远处。然而,这种方法虽然在思路上较为简单,但实际上实现起来难度极大,因此其讨论价值大于其实际价值。

那么我们可以考虑其他做法。比如:我们可以将每条线段的端点及与别的线段的交点与原点以及一个坐标值特别大的点(充当无穷远点)看作一个个图论中的结点,并将两个连线不与任何线段相交的结点之间连一条边,如果原点与无穷远点连通,也就代表动点可以移动到无穷远。

然而,上述算法其实是错误的,因为一道紫题不可能这么简单就完了在特殊情况下有反例。比如下图:

虽然算法本身是错误的,但其思路有一定的参考价值。比如,我们只需要解决上图中两条线段的交点所导致的一系列判断问题即可。具体方法是将每条线段两端各延长一些,这样原本的公共端点便成为了两条线段交点,因此很容易判断原点经过其的可行性。

清晰度感人

不过这样做仍有纰漏,比如下图:

解决方法比较简单:如果延长后的新端点在另一条线段上,那么此点不参与构图即可。

代码较为复杂,请认真阅读并参考注释理解。

代码实现

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-12;
double dcmp(double x) 
{
  if(fabs(x)<eps) 
    return 0;
  else 
    return x<0?-1:1;
}
struct Point 
{
  double x,y;
  Point(double x=0,double y=0):x(x),y(y){}
};
typedef Point Vector;
Vector operator+(const Point& A,const Point& B) 
{
  return Vector(A.x+B.x,A.y+B.y);
}
Vector operator-(const Point& A,const Point& B) 
{
  return Vector(A.x-B.x,A.y-B.y);
}
Vector operator*(const Point& A,double v) 
{
  return Vector(A.x*v,A.y*v);
}
Vector operator/(const Point& A,double v) 
{
  return Vector(A.x/v,A.y/v);
}
double Cross(const Vector& A,const Vector& B) 
{
  return A.x*B.y-A.y*B.x;
}
double Dot(const Vector& A,const Vector& B) 
{
  return A.x*B.x+A.y*B.y;
}
double Length(const Vector& A) 
{
  return sqrt(Dot(A,A));
}
bool operator<(const Point& p1,const Point& p2) 
{
  return p1.x<p2.x||(p1.x==p2.x && p1.y<p2.y);
}
bool operator==(const Point& p1,const Point& p2) 
{
  return p1.x==p2.x&&p1.y==p2.y;
}
bool SegmentProperIntersection(const Point& a1,const Point& a2,const Point& b1,const Point& b2) 
{
  double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),
  c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1);
  return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;
}//判断两条线段是否相交 
bool OnSegment(const Point& p,const Point& a1,const Point& a2) 
{
  return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p))<0;
}//判断点是否在线段上 
const int maxv=205;
int V,G[maxv][maxv],vis[maxv];
bool dfs(int u) 
{
  if(u==1) 
    return 1;
  vis[u]=1;
  for(int v=0;v<V;v++)
    if(G[u][v]&&!vis[v]&&dfs(v)) 
        return 1;
  return 0;
}
const int maxn=100+5;
int n;
Point p1[maxn],p2[maxn];
bool OnAnySegment(Point p) 
{
  for(int i=0;i<n;i++)
    if(OnSegment(p,p1[i],p2[i])) 
        return 1;
  return 0;
}//判断点是否在任意一条线段上 
bool IntersectWithAnySegment(Point a,Point b) 
{
  for(int i=0;i<n;i++)
    if(SegmentProperIntersection(a,b,p1[i],p2[i])) 
        return 1;
  return 0;
}//判断两点之间路径是否与其他任何线段相交
bool find_path() //构图 
{
  vector<Point>vertices;
  vertices.push_back(Point(0,0));//原点 
  vertices.push_back(Point(1e5,1e5));//无穷远点 
  for(int i=0;i<n;i++) 
  {
    //判断点是否在其他线段上 
    if(!OnAnySegment(p1[i])) 
        vertices.push_back(p1[i]);
    if(!OnAnySegment(p2[i])) 
        vertices.push_back(p2[i]);
  }
  V=vertices.size();
  memset(G,0,sizeof G);
  memset(vis,0,sizeof vis);
  for(int i=0;i<V;i++)
    for(int j=i+1;j<V;j++)
      if(!IntersectWithAnySegment(vertices[i],vertices[j]))
        G[i][j]=G[j][i]=1;//从 i 点到 j 点的路径不与其他任何线段相交,则认为从 i 点可以到 j 点,反之亦然。 
  return dfs(0);
}
int main() 
{
  while(cin>>n&&n) 
  {
    for(int i=0;i<n;i++) 
    {
      double x1,y1,x2,y2;
      cin>>x1>>y1>>x2>>y2;
      Point a=Point(x1,y1);
      Point b=Point(x2,y2);
      Vector v=b-a;
      v=v/Length(v);
      p1[i]=a-v*1e-6;
      p2[i]=b+v*1e-6;//将线段稍稍延长一些 
    }
    if(find_path()) 
        cout<<"no\n"; 
    else 
        cout<<"yes\n";
  }
  return 0;
}

参考文献

[1]刘汝佳,陈锋《算法竞赛入门经典训练指南》

update

本文最初发布于 2021.10.5 ,后由网友指出代码有问题,故于 2022.1.22 重新更改上传。

posted @ 2021-10-05 13:11  Hadtsti  阅读(1)  评论(0编辑  收藏  举报  来源