POJ1127 Jack Straws

题目链接:

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;
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值