HDU - 2150(借助向量叉乘判断线段相交)

题目链接https://vjudge.net/contest/368031#problem/C
题目描述:
经过激烈的争夺,Lele终于把那块地从Yueyue的手里抢了回来。接下来,Lele要开始建造他的灌溉系统。
通过咨询Lele的好友——化学系的TT,Lele决定在田里挖出N条沟渠,每条沟渠输送一种肥料。
每条沟渠可以看作是一条折线,也就是一系列线段首尾连接而成(除了第一条线段开头和最后一条线段的结尾)。由于沟渠很细,你可以忽略掉它的宽度。
由于不同的肥料之间混合会发生化学反应,所以修建的沟渠与沟渠之间不能相交。
现在TT给Lele画了一些设计图,Lele请你判断一下设计图中的沟渠与沟渠之间是否有相交。

Input
本题目包含多组测试,请处理到文件结束(EOF)。
每组测试的第一行有一个正整数N(0<N<30),表示管道的数目。接下来给出这N条管道的信息。
对于每条管道,第一行是一个正整数K(0<K<100),表示这条管道是由K个端点组成。
接下来的K行给出这K个端点信息。每个端点占一行,用两个整数X和Y(0<X,Y<1000)分别表示这个端点的横坐标和纵坐标的值。

Output
对于每组测试,如果该测试管道与管道之间有相交的话,输出"Yes",否则输出"No"。
Sample Input

2
2
0 0
1 1
2
0 1
1 0
2
2
0 0
1 1
2
1 0
2 1
2
3
0 0
1 1
2 1
2
2 0
3 0

Sample Output

Yes
No
No

输入输入解析:

输入:
2
2
0 0
1 1
2
0 1
1 0
输出:
Yes
第一个2表示有两条折线。接下来描述这n条折线的信息。第二行的2表示第一条折线有2个点组成。
接下来的两行表示这两个点的横纵坐标。接着输入下一个折线的点的个数和对应的坐标。

分析
每一条折线有若干点,枚举组成每一条折线的线段和其余折线的所有线段看是否相交。

知识点:

外积,又称叉积两个向量v1(x1, y1)和v2(x2, y2)的外积v1×v2=x1y2-y1x2
根据右手法则,如果由v1到v2是顺时针转动,外积为负,反之为正;为0表示二者方向相同(平行)。

考虑如何考虑判断两条线段相交?
在这里插入图片描述
AB和CD相交需满足C和D分别在AB的两侧A和B分别在CD的两侧

问题转化为如何证明两点分别在一条线的两侧? 例如证明 C和D分别在AB的两侧?
在这里插入图片描述

引入向量AC和AD
上图是AB和CD相交的情况,可以看出向量AB×AD < 0,AB×AC > 0
可以得出CD的2个端点C和D,与另一条线段的一个端点(A或B)连成的向量,与向量AB做叉乘结果异号则C和D分别在AB的两端。

特殊情况

在这里插入图片描述
AB和CD相交于点C,此时AB×AC=0,AB×AD <0。此时一个负的,一个为0,可以看出此时两条线段也是相交的。可以得出有一方叉乘的结果为0可以看成两条线段也是相交的

AB和CD的叉乘为0(即AB和CD平行):
在这里插入图片描述
可以看出:
在平行的前提下:可以分为共线和不共线两种情况

那如何判断共线?
我们可以在两条线段中各取一点,用这两点组成的向量与其中一条线段进行叉乘,结果若为0,就表示两线段共线。
在这里插入图片描述
可是共线却不一定相交,例如上图的第二种情况。

那如何判断相交?

解决方法:
我们给每条线段的两个端点排序:横坐标大的点更大,横坐标相同,纵坐标大的点更大。
排好序后,每条线段中,小的点当起点,大的当终点。

若一条线段AB与另一条线段CD共线,且线段AB的起点小于等于线段CD的起点,但线段AB的终点大于等于线段CD的起点。则两条线段相交。

在这里插入图片描述
代码:

#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
int n,flag;
int k[40];
struct node
{
    int x,y;
} s[40][110];
double f(node a,node b,node c)///如果AB.AC,得到的结果是垂直于ABC这个平面的一个向量
{
     ///两个向量的向量积,是一个垂直于两个向量的向量
    return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);///AB.AC=(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x)
}
int dfs(node a,node b,node c,node d)///组成两条线段的四个点
{
    if(max(a.x,b.x)>=min(c.x,d.x)&&min(c.x,d.x)<=max(a.x,b.x)&&
            max(a.y,b.y)>=min(c.y,d.y)&&min(c.y,d.y)<=max(a.y,b.y)&&
            f(a,b,c)*f(a,b,d)<=0&&f(d,c,a)*f(d,c,b)<=0)///ab×ac *  ab×ad
        return  1;
    return 0;
}
int main()
{
    while(~scanf("%d",&n))
    {
        memset(k,0,sizeof(k));
        flag=0;
        for(int i=0; i<n; i++)
        {
            scanf("%d",&k[i]);
            for(int j=0; j<k[i]; j++)
                scanf("%d%d",&s[i][j].x,&s[i][j].y);
        }
        if(n==1)
        {
            printf("No\n");
            continue;
        }
        for(int i=0; i<n-1; i++)///枚举每一条线段
            for(int j=1; j<k[i]; j++)///这条线段的所有点
                for(int u=i+1; u<n; u++)
                    for(int v=1; v<k[u]; v++)
                        if(dfs(s[i][j-1],s[i][j],s[u][v-1],s[u][v]))
                        {
                            flag=1;
                            break;
                        }
        if(flag)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zaiyang遇见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值