HDU 1558 计算几何、并查集-Segment set

Description
A segment and all segments which are connected with it compose a segment set. The size of a segment set is the number of segments in it. The problem is to find the size of some segment set.
这里写图片描述

Input
In the first line there is an integer t - the number of test case. For each test case in first line there is an integer n (n<=1000) - the number of commands.

There are two different commands described in different format shown below:

P x1 y1 x2 y2 - paint a segment whose coordinates of the two endpoints are (x1,y1),(x2,y2).
Q k - query the size of the segment set which contains the k-th segment.

k is between 1 and the number of segments in the moment. There is no segment in the plane at first, so the first command is always a P-command.

Output
For each Q-command, output the answer. There is a blank line between test cases.

Sample Input

1
10
P 1.00 1.00 4.00 2.00
P 1.00 -2.00 8.00 4.00
Q 1
P 2.00 3.00 3.00 1.00
Q 1
Q 3
P 1.00 4.00 8.00 2.00
Q 2
P 3.00 3.00 6.00 -2.00
Q 5

Sample Output

1
2
2
2
5

题意


如果线段A和线段B相交,线段B和线段C相交,那么线段A、B、C属于同一个线段集,现在给n次操作,当操作为P的时候添加一个线段,当操作为Q时查询第k个线段所在的线段集里面有多少个线段。


题解


本题是一个并查集的问题。
如果不了解什么是并查集,可以到下面这个链接里面去看,我觉得讲得很好,第一次了解并查集就是看这个看懂的。
http://blog.csdn.net/qzc295919009/article/details/23306781
如果你已经了解什么是并查集,那么本题的关键问题就是如何判断两条线段在同一个集合,也就是判断线段相交了。
线段相交在计算几何中用快速排斥实验和跨立实验来进行验证,一般应该不了解吧,可以自己上网搜,我就不贴链接了。
现在假设你已经了解了快速排斥实验和跨立实验,那我来讲讲代码实现。
假设有A1、A2、B1、B2四个点
1、快速排斥实验:
快速排斥实验是看以A1A2、B1B1为对角线的矩阵是否相交,如果不相交的话线段不可能相交,如果相交的话线段可能相交。
那么如何判断这两个矩阵是否相交呢,判断这个四个点的x、y区间是否相交就好了,可以到纸上面画一下,代码如下:

bool check()
{
    if(min(A1.x,A2.x) <= max(B1.x,B2.x) &&
       min(B1.x,B2.x) <= max(A1.x,A2.x) &&
       min(A1.y,A2.y) <= max(B1.y,B2.y) &&
       min(B1.y,B2.y) <= max(A1.y,A2.y))
    {
        if(kua())//kua()是跨立实验的函数
            return true;
    }
    return false;
}

2、跨立实验
如果线段A1A2和线段B1B2相交,那么A1、A2会分别在B1B2两边,则(A1-B1)X(B2-B1)和(A2-B1)X(B2-B1)异号,即:
(A1-B1)X(B2-B1)*(A2-B1)X(B2-B1)<0
如果在同一边,那么就会大于0,如果两个点都在边上,那么两条线段在同一条直线上,因为经过快速排斥实验所以也是相交的,所以我们可以用下面的表达式判断:
(A1-B1)X(B2-B1)*(B2-B1)X(A2-B1)>=0
同理,对于B1、B2两个点也一样
代码如下:

double xx(Point &s, Point &t)
{
    return (s.x*t.y-s.y*t.x);
}

double kua()
{
    A1B1.x = A1.x-B1.x; A1B1.y = A1.y - B1.y;
    B2B1.x = B2.x-B1.x; B2B1.y = B2.y - B1.y;
    A2A1.x = A2.x-A1.x; A2A1.y = A2.y - A1.y;
    B2A1.x = B2.x-A1.x; B2A1.y = B2.y - A1.y;
    A2B1.x = A2.x-B1.x; A2B1.y = A2.y - B1.y;
    if(xx(A1B1,B2B1)*xx(B2B1,A2B1)>=0)
    {
        A1B1.y = -A1B1.y; A1B1.x = -A1B1.x;
        if(xx(A1B1,A2A1)*xx(A2A1,B2A1)>=0)
            return 1;
        return 0;
    }
    return 0;
}

这样我们就可以判断两个线段是否相交,完整AC代码如下。


#include <iostream>
#include <stdio.h>
#include <cmath>
#define MAXN 1010
using namespace std;

struct Point
{
    double x,y;
};

struct Edge
{
    Point a,b;
};

Edge e[MAXN];
int fa[MAXN];
int sz[MAXN];
Point A1,A2,B1,B2;
Point A1B1, A2B1, B2B1, A2A1, B2A1;

double xx(Point &s, Point &t);
double kua();
bool check();
void init(int n);
int find(int x);
void merge(int u, int v);

int main()
{
    //freopen("in.txt", "r", stdin);
    int T,n;
    char mode[10];
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        init(n);
        int k = 0;
        for(int i = 0; i < n; i++)
        {
            scanf("%s", &mode);
            if(mode[0] == 'P')
            {
                k++;
                scanf("%lf%lf%lf%lf",&e[k].a.x,&e[k].a.y,&e[k].b.x,&e[k].b.y);
                B1 = e[k].a; B2 = e[k].b;
                for(int j = 1; j < k; j++)
                {
                    A1 = e[j].a; A2 = e[j].b;
                    if(find(j) != find(k) && check())
                    {
                        //cout << j << " " << k << endl;
                        merge(j,k);
                    }
                }
            }
            else
            {
                int tmp;
                scanf("%d", &tmp);
                printf("%d\n", sz[find(tmp)]);
            }
        }
        if(T != 0)
            printf("\n");
    }
    return 0;
}

double xx(Point &s, Point &t)
{
    return (s.x*t.y-s.y*t.x);
}

double kua()
{
    A1B1.x = A1.x-B1.x; A1B1.y = A1.y - B1.y;
    B2B1.x = B2.x-B1.x; B2B1.y = B2.y - B1.y;
    A2A1.x = A2.x-A1.x; A2A1.y = A2.y - A1.y;
    B2A1.x = B2.x-A1.x; B2A1.y = B2.y - A1.y;
    A2B1.x = A2.x-B1.x; A2B1.y = A2.y - B1.y;
    if(xx(A1B1,B2B1)*xx(B2B1,A2B1)>=0)
    {
        A1B1.y = -A1B1.y; A1B1.x = -A1B1.x;
        if(xx(A1B1,A2A1)*xx(A2A1,B2A1)>=0)
            return 1;
        return 0;
    }
    return 0;
}

bool check()
{
    if(min(A1.x,A2.x) <= max(B1.x,B2.x) &&
       min(B1.x,B2.x) <= max(A1.x,A2.x) &&
       min(A1.y,A2.y) <= max(B1.y,B2.y) &&
       min(B1.y,B2.y) <= max(A1.y,A2.y))
    {
        if(kua())
            return true;
    }
    return false;
}

void init(int n)
{
    for(int i = 1; i <= n; i++)
    {
        sz[i] = 1;
        fa[i] = i;
    }
}

int find(int x)
{
    return fa[x] == x?x:fa[x]=find(fa[x]);
}

void merge(int u, int v)
{
    int fu = find(u), fv = find(v);
    if(fu != fv)
    {
        fa[fv] = fu;
        sz[fu] += sz[fv];
        sz[fv] = 0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值