题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=1558
题目类型: 计算集合 , 并查集
题目:
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.
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
题目大意:
输入几条线段,线段由坐标上的两点组成, 每一点有x,y,代表x轴和y轴对应的值。
当输入为Q k时, 输出与第k条直线直接或间接有相连的线段条数。
分析与总结:
这一题是并查集比较基础的应用, 但是这题的关键在于判断两条直线是否相交。
断两线段是否相交的方法:
我们分两步确定两条线段是否相交:
1. 快速排斥试验:设以线段 P1P2 为对角线的矩形为R, 设以线段 Q1Q2 为对角线的矩形为T,如果R和T不相交,显然两线段不会相交。
2. 跨立试验:如果两线段相交,则两线段必然相互跨立对方。
若P1P2跨立Q1Q2 ,则矢量 ( P1 - Q1 ) 和( P2 - Q1 )位于矢量( Q2 - Q1 ) 的两侧,即:
(( P1 - Q1 ) × ( Q2 - Q1 )) * (( P2 - Q1 ) × ( Q2 - Q1 )) < 0。(利用了向量叉积)
当 ( P1 - Q1 ) × ( Q2 - Q1 ) = 0 时,说明 ( P1 - Q1 ) 和 ( Q2 - Q1 )共线,但是因为已经通过快速排斥试验,所以 P1 一定在线段 Q1Q2上;同理,( Q2 - Q1 ) ×(P2 - Q1 ) = 0 说明 P2 一定在线段 Q1Q2上。
所以判断P1P2跨立Q1Q2的依据是:(( P1 - Q1 ) × ( Q2 - Q1 )) * (( Q2 - Q1 ) × ( P2 - Q1 )) >= 0。
同理判断Q1Q2跨立P1P2的依据是:(( Q1 - P1 ) × ( P2 - P1 )) * (( P2 - P1 ) × ( Q2 - P1 )) >= 0。
具体情况如下图所示:(这里利用了向量叉积来判断两个向量是否位于另一向量的两侧)
注意:只有同时满足以上两个条件,即相互跨立对方,两个线段才相交。
代码:
struct Point{
double x,y;
};
double direction(Point p0,Point p1,Point p2)
{
return (p2.x-p0.x)*(p1.y-p0.y)-(p1.x-p0.x)*(p2.y-p0.y);
}
bool on_segment(Point p0,Point p1,Point p2)
{
if((min(p0.x,p1.x)<=p2.x && p2.x<=max(p0.x,p1.x)) && (min(p0.y,p1.y)<=p2.y && p2.y<=max(p0.y,p1.y)))
return true;
return false;
}
bool Segment_intersect(Point p1,Point p2,Point p3,Point p4)
{
double d1,d2,d3,d4;
d1 = direction(p3,p4,p1);
d2 = direction(p3,p4,p2);
d3 = direction(p1,p2,p3);
d4 = direction(p1,p2,p4);
if(((d1>0 && d2<0)||(d1<0 && d2>0)) && ((d3>0 && d4<0)||(d3<0&&d4>0)))
return true;
else if(d1==0 && on_segment(p3,p4,p1))
return true;
else if(d2==0 && on_segment(p3,p4,p2))
return true;
else if(d3==0 && on_segment(p1,p2,p3))
return true;
else if(d4==0 && on_segment(p1,p2,p4))
return true;
return false;
}
知道了怎样判断两线是否相交后, 一切都好办。
接下来要做的是, 每次添加一条直线是,都进行并查集的Union操作。
如果要知道第k条线所在的集合有几条线段,只需要判断并查集中所有以k的跟结点为跟结点的数量有几个即可
AC代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define N 1005
using namespace std;
struct Point2D //二维平面点
{
double x,y;
Point2D():x(0),y(0){}
Point2D(double x,double y):x(x),y(y){}
double Mod() const {return sqrt(x*x + y*y);}
friend Point2D operator-(const Point2D& lh,const Point2D& rh){
return Point2D(lh.x-rh.x, lh.y-rh.y);
}
friend double operator&(const Point2D& lh,const Point2D& rh){
return lh.x*rh.y - lh.y*rh.x;
}
friend std::istream& operator>>(std::istream& in, Point2D& pt){
in>>pt.x>>pt.y;
return in;
}
};
struct Segment2D{
Point2D bgn, end;
Segment2D():bgn(),end(){}
Segment2D(Point2D b,Point2D e):bgn(b),end(e){}
Segment2D(double x1,double y1,double x2,double y2):bgn(x1,y1),end(x2,y2){}
friend std::istream& operator>>(std::istream& in, Segment2D& pt){
in>>pt.bgn>>pt.end;
return in;
}
friend std::ostream& operator<< (std::ostream& out, Segment2D& pt){
out<<pt.bgn.x<<" "<<pt.bgn.y<<" "<<pt.end.x<<" "<<pt.end.y;
return out;
}
};
bool SegmentIntersect(const Segment2D& u, const Segment2D& v)
{
//1.快速排斥试验,不相交返回0
if((max(u.bgn.x,u.end.x)>=min(v.bgn.x,v.end.x))&&
(max(v.bgn.x,v.end.x)>=min(u.bgn.x,u.end.x))&&
(max(u.bgn.y,u.end.y)>=min(v.bgn.y,v.end.y))&&
(max(v.bgn.y,v.end.y)>=min(u.bgn.y,u.end.y)));
else return false;
//2.跨立实验,u的两端点在v两侧,并且v的两端点在u两侧
if((((u.bgn-v.bgn)&(v.end-v.bgn))*((v.end-v.bgn)&(u.end-v.bgn))>=0)&&
(((v.bgn-u.bgn)&(u.end-u.bgn))*((u.end-u.bgn)&(v.end-u.bgn))>=0))
return true;
else return false;
}
Segment2D arr[N];
int Index, dnum[N];
int father[N];
void init(){
for(int i=0; i<N; ++i)
father[i]=i, dnum[i] = 1;
}
int find(int x){
int i, j=x;
while(j!=father[j]) j=father[j];
while(x!=j){
i = father[x];
father[x] = j;
x = i;
}
return j;
}
void Union(int x, int y){
int a = find(x);
int b = find(y);
if(a!=b)
father[a] = b;
}
int main(){
freopen("input.txt", "r", stdin);
int T,n,k,cas=1;
char cmd[2];
scanf("%d",&T);
while(T--){
scanf("%d", &n);
memset(dnum, 0, sizeof(dnum));
Index=0;
init();
while(n--){
scanf("%s", cmd);
if(cmd[0]=='P'){
cin >> arr[++Index];
for(int i=1; i<=Index-1; ++i){
if(SegmentIntersect(arr[i], arr[Index]))
Union(i, Index);
}
}
else if(cmd[0] == 'Q'){
scanf("%d", &k);
int x=find(k);
int cnt=0;
for(int i=1; i<=Index; ++i){
if(x==find(i))
++cnt;
}
cout << cnt << endl;
}
}
if(T) printf("\n");
}
return 0;
}
—— 生命的意义,在于赋予它意义。
分享到:
相关推荐
hdu 1695 GCD(欧拉函数+容斥原理).docx
杭电ACM课件2014版之 (HDUACM201403版_06)并查集(最小生成树)
acm hdu as easy as a+b
(HDUACM2010版_06)并查集(最小生成树(HDUACM2010版_06)并查集(最小生成树(HDUACM2010版_06)并查集(最小生成树(HDUACM2010版_06)并查集(最小生成树
杭电ACM课件2014版之 (HDUACM201403版_08)计算几何基础
ACM算法 计算几何基础 用于计算不规则多边形,凹多边形和凸多边形
ACM题库,一些题目和答案,以及解题报告,传上来共享
【HDU 3993】田忌赛马 题解+勘误 题解这里就略写一下了,主要是勘误。 这道题是2011年之前的多校训练题,2020年的今天,我们一个集训队全部挂在上面了。最后在HDU看到了9年前的讨论区,才知道这题有如下问题: speed...
hdu acm 教案 计算几何基础 hdu acm 教案 计算几何基础
acm 技术大牛 课件 HDU 自学必备课件 全套齐全 (lecture_01)初识ACM (lecture_02)简单数学题 (lecture_03)递推求解 (lecture_04)动态规划(1)_ (lecture_05)计算几何基础_ (lecture_06)母函数 ...并查集
杭电OnlineJudge 200-2099的解题报告
hdu 3341(ac自动机+状态压缩) 题意:容易理解... 思路:首先一开始容易想到要用到dp,开设一个dp[41][41][41][41][501]的数组来解决,但是明显内存已经超出范围了,于是就想如何减少内存呢?只要知道A、T、C、G其中...
现在,给你两个正的小数A和B,你的任务是代表大明计算出A+B的值。 Input 本题目包含多组测试数据,请处理到文件结束。 每一组测试数据在一行里面包含两个长度不大于400的正小数A和B。 Output 请在一行里面...
本人准备2020年保研机试时刷的题目(虽然最后机试取消了,...来自某中流985,在HDU和vjudge平台上大概刷了400道。本文件地图(excel表格)包含了绝大部分我刷过的题目,笔记中具有思路、代码、总结和心得。 大佬勿入!
ACM课件!!(lecture_07)计算几何基础_easy ACM课件!!(lecture_07)计算几何基础_easy hdu acm usa 测试数据 算法
使用并查集+贪心:先将已有边的权值从大到小排序,又n个点只需n-1条边,这时再遍历一遍,将有边的两点合并为一个队伍,当边的数量达到n-1时退出循环,因为此时已达到最小生成树。 边的权值由大到小排序是因为要将大...
300+ AC 代码 。 大数 , 线段树 , 字符串 , dp.....
HDU的1250,主要是利用高精度加法,但是代码有点繁琐,效率不是很高