题目链接:
http://poj.org/problem?id=1127
解题思路:
这道题目问的是:给你一些线段,求出哪些线段是相连的,哪些是不相连的。相连包括间接相连,即这两条线段本身不直接相连,而是通过其它线段的连接而间接相连。解决这道题目的关键要解决两个问题:1.判断两条线段是否直接相连,即它们相交与否,这是一个几何问题;2.如果某两条线段不相交,那么它们是否通过其它线段的连接而间接相连。如果把相连的线段看做是一个集合,这个问题变成判断这两条线段是否处于同一个集合中,我们可以用并查集这个数据结构去解决这个问题。
对于第一个问题,判断两条线段是否相交,主要分两个步骤。
1.快速排斥试验。如果以线段p1p2为对角线的矩形R1与以线段q1q2为对角线的矩形R2不相交,则线段p1p2和q1q2不相交。写成代码可以这样写:
if(max(p1.x,p2.x)<min(q1.x,q2.x)||max(q1.x,q2.x)<min(p1.x,p2.x)||max(p1.y,p2.y)<min(q1.y,q2.y)||max(q1.y,q2.y)<min(p1.y,p2.y)) then 不相交;
2.跨立试验。如果线段p1p2与线段q1q2相交,则它们一定相互跨立对方。若线段q1q2跨立线段p1p2,则向量p1-q1和向量p2-q1一定位于向量q2-q1的两侧。同理,线段p1p2跨立q1q2,则向量q1-p1和向量q2-p1一定位于向量p2-p1的两侧。写成代码可以这样写:
if(multiply(q1,q2,p1)*multiply(q1,q2,p2)<0&&multiply(p1,p2,q1)*multiply(p1,p2,q2)<0) then 相交;
其中multiply函数的定义是:
/*计算向量p1p2与向量p1p3的叉积,若p1p3在p1p2的逆时针方向,则返回>0,顺时针方向返回<0*/
int multiply(Point p1,Point p2,Point p3)
{
return (p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x);
}
如果multiply(q1,q2,p1)*multiply(q1,q2,p2)等于0或者multiply(p1,p2,q1)*multiply(p1,p2,q2)等于0,说明两条线段中至少有一条线段的一端位于另一条线段之上,也属于相交的情况(两条线段在同一直线上但是不相连也可以使上面的两条式子等于0,但是无法通过快速排斥试验)。至此,第一个问题得到解决。
对于第二个问题,我们可以用并查集去解决。掌握并查集,关键要掌握并查集的两个操作,查询最远祖先和合并不相交的集合。
并查集是一种树形的结构,以双亲表示法存储结点。两个结点是否在同一集合中,要看他们的最远祖先(根结点)是否相同。查找最远祖先可以用递归的方法,由于树形结构容易退化成一条长链,从而使得查找效率低下,因此在查找最远祖先的同时要进行路径压缩。以father[i]表示第i条线段的双亲结点的编号,查询最远祖先的代码如下:
int findRoot(int v)
{
if(father[v]==v)
return v;
else
{
father[v]=findRoot(father[v]);//路径压缩
return father[v];
}
}
并查集的合并操作建立在查找操作的基础上,同时可以用rank[i]表示以第i个结点作为根结点的树的最大深度,合并的时候把深度小的树合并到深度大的树上。
void unionSet(int x,int y)
{
int fx=findRoot(x);
int fy=findRoot(y);
if (rank[fx]>rank[fy])//比较两个集合的深度进行合并
father[fy]=fx;
else
{
father[fx]=fy;
if(rank[fx]==rank[fy])
rank[fy]++;
}
}
具体代码:
#include<iostream>
using namespace std;
struct Point
{
int x,y;
};
struct Segment
{
Point a,b;
};
Segment seg[20];
int father[20],rank[20];
/*计算向量p1p2与向量p1p3的叉积,若p1p3在p1p2的逆时针方向,则返回>0,顺时针方向返回<0*/
int multiply(Point p1,Point p2,Point p3)
{
return (p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x);
}
bool isIntersect(Point p1,Point p2,Point q1,Point q2)
{
if(max(p1.x,p2.x)<min(q1.x,q2.x)||max(q1.x,q2.x)<min(p1.x,p2.x)||max(p1.y,p2.y)<min(q1.y,q2.y)||max(q1.y,q2.y)<min(p1.y,p2.y))
return 0;
if(multiply(q1,q2,p1)*multiply(q1,q2,p2)<=0&&multiply(p1,p2,q1)*multiply(p1,p2,q2)<=0)
return 1;
else
return 0;
}
int findRoot(int v)
{
if(father[v]==v)
return v;
else
{
father[v]=findRoot(father[v]);//路径压缩
return father[v];
}
}
void unionSet(int x,int y)
{
int fx=findRoot(x);
int fy=findRoot(y);
if (rank[fx]>rank[fy])//比较两个集合的深度进行合并
father[fy]=fx;
else
{
father[fx]=fy;
if(rank[fx]==rank[fy])
rank[fy]++;
}
}
int main()
{
int n;
while(cin>>n&&n)
{
for(int i=1;i<=n;i++)
{
cin>>seg[i].a.x>>seg[i].a.y>>seg[i].b.x>>seg[i].b.y;
father[i]=i;
rank[i]=0;
}
for(int i=2;i<=n;i++)
for(int j=1;j<i;j++)
if(findRoot(i)!=findRoot(j)&&isIntersect(seg[i].a,seg[i].b,seg[j].a,seg[j].b))
unionSet(i,j);
int a,b;
while(cin>>a>>b)
{
if(a==0&&b==0)
break;
if(findRoot(a)==findRoot(b))
cout<<"CONNECTED"<<endl;
else
cout<<"NOT CONNECTED"<<endl;
}
}
return 0;
}