判断线段相交(hdu1558 Segment set 线段相交+并查集)
先说一下题目大意:给定一些线段,这些线段顺序编号,这时候如果两条线段相交,则把他们加入到一个集合中,问给定一个线段序号,求在此集合中有多少条线段。
这个题的难度在于怎么判断线段相交,判断玩相交之后就是怎么找个他们之间的联系,这时候就要用到并查集了。
步骤:
1.判断两条线段相交
2. 用并查集实现查找线段个数和添加到集合中
关于这个判断线段相交的问题。我搞了一晚上加上一下午,刚开始自己想了一种数学上的相交,就是先求出两条线段所在的线性方程,然后求出他们的交点,最后在判断这个交点在不在这两个线段之间。这种方式刚开始一想挺简单的,但是在判断在不在两个线段之间就显得比较麻烦了,而且刚开始还漏了一种情况,就是在他们斜率不存在时怎么求出方程来,当时没有考虑这一点直接wa了,后来又加上了这种情况才AC了。
还有一种方法就是利用向量来判定线段是否相交,这个我是看的算法导论上的,答题思路就是判断其中一条线段是否横跨另一条线段,如果这两条线段都互相横跨另一条,那么一定相交,当然还有边界条件,就是这个交点是边界在线段的终点的情况,那具体怎么判断一条线段是否横跨另外一条线段呢,这时候用到向量了,两个向量p1,p2,如果他们的叉乘积大于0,就说明p1位于p2的逆时针的方向,小于0顺时针,等于0共线。所以这一步很关键。当解决了这个问题之后,那么就好办了,如果一条线段的一个点在顺时针侧,一个点在逆时针侧,那么这条线段横跨另一条直线,注意是直线,还不是线段,如果同时另外一条线段也横跨这一条,那么这时就是两个线段相互横跨了,就是相交了。还有一个关键点就是在边界情况下(就是一条线段的一个端点在另一条线段上的时候)怎么办,这时候用到上面写好的函数来判断,如果返回值是0,那么就是临界条件,这时候判断其中一个端点和另外一条线段的关系就行了,如果都不满足上面的这些情况,肯定是不相交了。
还有一点需要注意,在利用并查集实现的时候,要用两个,一个是普通的并查集来存放它们之间的关系,还有一个是存放它们当前集合的数目。具体代码如下;
代码一(第一种判断线段相交的方式)(后来证明可能有些数据过不了,不建议用这个)
#include<iostream>
#include <cstdio>
#include <cstring>
#define EPS 1e-8
using namespace std;
const int N = ;
struct point{
double x, y;
};
struct segmnt{
point ori, des;
};
int father[N], num[N];//father用来保存相交的线段的集合,num表示当前线段所在集合有多少条线段
segmnt seg[N];
//初始化
void init(int n)
{ for (int i = ; i <= n; i++)
{
father[i] = i;
num[i] = ;
}
}
//判断线段是否相交
bool is_Cross(segmnt s1, segmnt s2)
{
point p1, p2, p3, p4;
p1 = s1.ori; p2 = s1.des;
p3 = s2.ori; p4 = s2.des;
//当第一条线段斜率不存在,第二条斜率存在时
if (p1.x == p2.x && p3.x != p4.x)
{
double k = (p4.y - p3.y) / (p4.x - p3.x);
double y = k * (p1.x - p3.x) + p3.y;
return ((y - p1.y >= EPS && y - p2.y <= EPS) || (y - p1.y <= EPS && y - p2.y >= EPS));
}
//当第一条线段斜率存在,第二条斜率不存在时
else if (p3.x == p4.x && p1.x != p2.x)
{
double k = (p2.y - p1.y) / (p2.x - p1.x);
double y = k * (p3.x - p1.x) + p1.y;
return ((y - p3.y >= EPS && y - p4.y <= EPS) || (y - p3.y <= EPS && y - p4.y >= EPS));
}
//当第一条第二条斜率都不存在时
else if (p1.x == p2.x && p3.x == p4.x)
{
return p1.x == p3.x;
}
//当他们斜率都存在时,先求出方程,然后求出他们的交点,判断交点是否在两条线段上
double k1 = (s1.des.y - s1.ori.y) / (s1.des.x - s1.ori.x);
double k2 = (s2.des.y - s2.ori.y) / (s2.des.x - s2.ori.x);
double x0 = (k1 * s1.ori.x - k2 * s2.ori.x + s2.ori.y - s1.ori.y) / (k1 - k2);
double y0 = k1 * (x0 - s1.ori.x) + s1.ori.y;
if (((x0 >= s1.ori.x && x0 <= s1.des.x) || (x0 >= s1.des.x && x0 <= s1.ori.x)) && ((y0 >= s1.ori.y && y0 <= s1.des.y) || (y0 >= s1.des.y && y0 <= s1.ori.y)) && ((x0 >= s2.ori.x && x0 <= s2.des.x) || (x0 >= s2.des.x && x0 <= s2.ori.x)) && ((y0 >= s2.ori.y && y0 <= s2.des.y) || (y0 >= s2.des.y && y0 <= s2.ori.y)))
return true;
return false;
}
//并查集查找
int find(int x)
{
while (x != father[x])
x = father[x];
return x;
}
//合并
void merge(int a, int b)
{
int ta = find(a);
int tb = find(b);
if (ta != tb)
{
father[ta] = tb;
num[tb] += num[ta];
}
}
int main()
{
int t, n;
scanf("%d", &t);
while (t--)
{
int index = ;
scanf("%d", &n);
init(n);
char option;
for (int i = ; i < n; i++)
{
getchar();
scanf("%c", &option);
if (option == 'P')
{
++index;
scanf("%lf %lf %lf %lf", &seg[index].ori.x, &seg[index].ori.y, &seg[index].des.x, &seg[index].des.y);
for (int i = ; i < index; i++)
if (is_Cross(seg[i], seg[index]))
merge(i, index);
}
else
{
int k;
scanf("%d", &k);
printf("%d\n", num[find(k)]);
}
}
if (t)
puts("");
}
return ;
}
代码二(第二种判断线段相交方式)
#include<iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = ;
struct point{
double x, y;
};
int father[N], num[N];//father用来保存相交的线段的集合,num表示当前线段所在集合有多少条线段
point a[N], b[N];//a代表一个线段的起点,b代表终点
//初始化
void init(int n)
{ for (int i = ; i <= n; i++)
{
father[i] = i;
num[i] = ;
}
}
double Min(double a, double b)
{
return a < b ? a : b;
}
double Max(double a, double b)
{
return a > b ? a : b;
}
//用来判断点c在线段ab的哪一侧,如果返回正值就是逆时针那侧,如果是负值就是顺时针那侧
double direction(point a, point b, point c)
{
point t1, t2;
t1.x = c.x - a.x; t1.y = c.y - a.y;
t2.x = b.x - a.x; t2.y = b.y - a.y;
return (t1.x * t2.y - t1.y * t2.x);
}
//前提条件已知ac和ab共线了,判断点c是否在线段ab上,如果是就返回true;
bool onSegment(point a, point b, point c)
{
double minx, miny, maxx, maxy;
minx = Min(a.x, b.x);
maxx = Max(a.x, b.x);
miny = Min(a.y, b.y);
maxy = Max(a.y, b.y);
return (c.x >= minx && c.x <= maxx && c.y >= miny && c.y <= maxy);
}
//判断线段是否相交,线段一是p1p2, 线段二是p3p4,如果相交返回true;
bool segment_intersect(point p1, point p2, point p3, point p4)
{
//判断p1在线段p3p4的哪一侧
double d1 = direction(p3, p4, p1);
//判断p2在线段p3p4的哪一侧
double d2 = direction(p3, p4, p2);
//判断p3在线段p1p2的哪一侧
double d3 = direction(p1, p2, p3);
//判断p4在线段p1p2的哪一侧
double d4 = direction(p1, p2, p4);
//如果相互跨越
if (d1 * d2 < && d3 * d4 < )
return true;
//下面四个是边界情况,第一个是点p1在线段P3p4上的时候
else if (d1 == && onSegment(p3, p4, p1))
return true;
else if (d2 == && onSegment(p3, p4, p2))
return true;
else if (d3 == && onSegment(p1, p2, p3))
return true;
else if (d4 == && onSegment(p1, p2, p4))
return true;
return false;
}
//并查集查找
int find(int x)
{
while (x != father[x])
x = father[x];
return x;
}
//合并
void merge(int a, int b)
{
int ta = find(a);
int tb = find(b);
if (ta != tb)
{
father[ta] = tb;
num[tb] += num[ta];
}
}
int main()
{
int t, n;
scanf("%d", &t);
while (t--)
{
int index = ;
scanf("%d", &n);
init(n);
char option;
for (int i = ; i < n; i++)
{
getchar();
scanf("%c", &option);
if (option == 'P')
{
++index;
scanf("%lf %lf %lf %lf", &a[index].x, &a[index].y, &b[index].x, &b[index].y);
for (int i = ; i < index; i++)
if (segment_intersect(a[i], b[i], a[index], b[index]))
merge(i, index);
}
else
{
int k;
scanf("%d", &k);
printf("%d\n", num[find(k)]);
}
}
if (t)
puts("");
}
return ;
}
判断线段相交(hdu1558 Segment set 线段相交+并查集)的更多相关文章
- Codeforces 938G 线段树分治 线性基 可撤销并查集
Codeforces 938G Shortest Path Queries 一张连通图,三种操作 1.给x和y之间加上边权为d的边,保证不会产生重边 2.删除x和y之间的边,保证此边之前存在 3.询问 ...
- 线段树、最短路径、最小生成树、并查集、二分图匹配、最近公共祖先--C++模板
线段树(区间修改,区间和): #include <cstdio> #include <iostream> #include <cstring> using name ...
- BZOJ3237:[AHOI2013]连通图(线段树分治,并查集)
Description Input Output Sample Input 4 5 1 2 2 3 3 4 4 1 2 4 3 1 5 2 2 3 2 1 2 Sample Output Connec ...
- bzoj2049 线段树 + 可撤销并查集
https://www.lydsy.com/JudgeOnline/problem.php?id=2049 线段树真神奇 题意:给出一波操作,拆边加边以及询问两点是否联通. 听说常规方法是在线LCT, ...
- Codeforces 1140F Extending Set of Points 线段树 + 按秩合并并查集 (看题解)
Extending Set of Points 我们能发现, 如果把x轴y轴看成点, 那么答案就是在各个连通块里面的x轴的个数乘以y轴的个数之和. 然后就变成了一个并查集的问题, 但是这个题目里面有撤 ...
- cf1278D——树的性质+并查集+线段树/DFS判环
昨天晚上本来想认真打一场的,,结果陪女朋友去了.. 回来之后看了看D,感觉有点思路,结果一直到现在才做出来 首先对所有线段按左端点排序,然后用并查集判所有边是否联通,即遍历每条边i,和前一条不覆盖它的 ...
- BZOJ 1018: [SHOI2008]堵塞的交通traffic(线段树分治+并查集)
传送门 解题思路 可以离线,然后确定每个边的出现时间,算这个排序即可.然后就可以线段树分治了,连通性用并查集维护,因为要撤销,所以要按秩合并,时间复杂度\(O(nlog^2 n)\) 代码 #incl ...
- 牛客多校第八场E Explorer(左开右闭线段树+可撤回并查集)题解
题意: 传送门 有\(n\)个点构成一个无向图,每条边有\(L_i,R_i\)表示这条边只能允许编号为\(L_i\dots R_i\)的人通过,现在问你最多有几个人能从\(1\)走到\(n\). 思路 ...
- 判断无向图是否有环路的方法 -并查集 -BFS
可以利用并查集或者带颜色标记的BFS(来自算法导论)判断. 首先介绍第一种,用并查集来判断: 首先初始化所有元素的根为-1,-1代表根节点,接下来对于图中的每一条边(v1,v2)都并入集合,并入的方式 ...
随机推荐
- [Head First设计模式笔记]----命令模式
命令模式定义:将“请求”封装成对象,以便使用不同的请求.队列或者日志来参数化其他对象.命令模式也支持可撤销的操作. 类图: 适用设计方案举例:实现一种遥控器,该遥控器具有七个可编程的插槽(每个都可以指 ...
- MySQL中对varchar类型排序问题
在数据库表中有一个对varchar类型的数值进行desc排序,很简单的要求吧.可是奇怪的现象出现了表中的数据不会根据从高到底进行排序了瞬间有点泪奔的感觉呀还好经过高手指点啊.所以想和大家分享一下希望下 ...
- python之sys模块
38.python的sys模块: 用于提供对Python解释器相关的操作: 1 2 3 4 5 6 7 8 9 sys.argv 命令行参数List,第一个元素是程序本身路径 sy ...
- python -i filename
今天学习的时候看见python -i filaname 这个命令,书上说使用这个命令可以去执行filename文件中的代码.但是今天在使用的时候却一直报错,经过多次测试才把问题解决. 原来这个命令是不 ...
- c构造函数
构造函数 任何一们面向对象语言里都会涉及构造函数这一概念,只是实现的方式各有差异.需要这main函数之前执行一段代码是非常容易的事情,只需要声明一对象的全局变量,在构造函数可以为所欲为干你想干的事 ...
- 转:Centos6.3添加解码器播放MP3和常见视频音频
原文来自于:http://blog.csdn.net/odaynot/article/details/8462273 参考地址: http://wiki.centos.org/AdditionalRe ...
- Ftp的断点下载实现
思路:首先获取本地临时文件的大小,在通过FTp的REST命令获取远程文件的偏移,然后再RETR命令在偏移处下载.while循环对比本地文件和远程文件的字节大小,如此 不断的反复以上过程,直到远程文件字 ...
- 【细说Java】Java封箱拆箱的一些问题
1.概念 首先简单介绍一下概念性的东西: 所谓封箱:就是把基本类型封装成其所对应的包装类型: 而拆箱则恰好相反,是把包装类型转换成其所对应的基本数据类型. 如基本类型int,封箱后的包装类是Integ ...
- 【贪心】XMU 1061 Ckp的约会
题目链接: http://acm.xmu.edu.cn/JudgeOnline/problem.php?id=1061 题目大意: n个任务(n<=1000),给定名字和开始.结束时间,求最多能 ...
- 元素水平垂直居中(transform,margin,table-cell,jQuery)
1.水平居中 .div{ margin:0 auto; (或者 margin:auto;) width:500px; height:300px; } 2.使用margin水平垂直居中 方式一: .di ...