hihoCoder 1040-矩形判断
#1040 : 矩形判断
时间限制:1000ms
单点时限:1000ms
内存限制:256MB
描述
给出平面上4条线段,判断这4条线段是否恰好围成一个面积大于0的矩形。
输入
输入第一行是一个整数T(1<=T<=100),代表测试数据的数量。
每组数据包含4行,每行包含4个整数x1, y1, x2, y2 (0 <= x1, y1, x2, y2 <= 100000);其中(x1, y1), (x2,y2)代表一条线段的两个端点。
输出
每组数据输出一行YES或者NO,表示输入的4条线段是否恰好围成矩形。
样例输入
3
0 0 0 1
1 0 1 1
0 1 1 1
1 0 0 0
0 1 2 3
1 0 3 2
3 2 2 3
1 0 0 1
0 1 1 0
1 0 2 0
2 0 1 1
1 1 0 1
样例输出
YES
YES
NO
这一题题意很简单,但是要写代码实现还是有很多细节需要考虑。
题目给出了4条
线段的8个端点,问是否能由这4条线段构成一个
面积大于0的矩形。脑海中立马浮现出一个矩形的形状。要构成一个矩形的首要条件是:
1. 有且只有4个互异的顶点
这个条件是显而易见的,如下图(1)给出的4条线段,虽然延长一下能组成矩形,但是很明显如果只由
线段组成的话,不是一个矩形。

要判断给出的点是否只有4个点,第一反应是用set集合,只要把题目给出的8个点都加入到set中,判断集合大小是否是4就可以了。
首先给出点的定义:
[cpp]
//点结构体
typedef struct P
{
int x,y;//两点坐标
bool operator<(const P& p)const//如果要加入到set中,需要重载<
{
//return this->x<p.x&&this->y<p.y;//注意这样会导致(0,0)<(0,1)且(0,1)<(0,0)的情况
return (this->x<p.x)||((this->x==p.x)&&(this->y<p.y));
}
bool operator==(const P&p)const//重载等于比较
{
return (this->x==p.x)&&(this->y==p.y);
}
};
[/cpp]
需要注意一点,因为需要把P加入到set中,而set是通过红黑树来排序的,所以需要重载小于<操作符。我最开始重载函数是这样写的
[cpp]
return this->x<p.x&&this->y<p.y;
[/cpp]
但是这样会有问题,如果给出两个点(0,0)和(0,1),会得出(0,0)<(0,1)和(0,1)<(0,0)
都不成立的结论,也就是说无法给(0,0)和(0,1)排序,也就无法插入到set中,所以需要修改成
[cpp]
return (this->x<p.x)||((this->x==p.x)&&(this->y<p.y));
[/cpp]
这样就能判断(0,0)<(0,1)成立了。有关这个问题可以参考
这个和
这个。
经过以上的步骤,就把问题转换成已知4个点,问能否由这4个点构成一个矩形的问题了。
此时又可以得出以下两个结论
2. 矩形中某条边和另外3条边的关系是:和其中2条边垂直,和另外1条边平行
3. 且和它平行的那条边不能是重合的边
有的同学可能会说,像上图中的(2)和(3)也是边a和另外2条边垂直、1条边平行啊,但是他们都不是矩形,但是大家别忘了,当我们走到这一步的时候,已经经过了第1个条件的检验,也就是只有4个顶点,你看图(2)和(3)明显不止4个顶点啊。所以结论2成立。
又有同学说,为什么还要结论3呢?因为还可能出现如上图(4)的样子,虽然图中所有边都满足条件2:和2条边垂直、和1条边平行,但是这明显不是一个矩形。因为边a和b重合了。所以结论3还是有必要的。
有了以上3条结论,我们就可以判断给出的4条线段是否能组成一个矩形了。
编程中有一些数学知识需要掌握的是,已知两个线段的4个端点,怎样判断这两条线段的关系:平行?垂直?其他?
很自然想到用斜率,如果k1*k2==-1则垂直,如果k1==k2则平行,否则其他。但是会遇到线段和y轴平行的情况,此时斜率是不存在的,所以需要稍微变一下。
假设线段L1的两个端点分别为(x1,y1)和(x2,y2),线段L2的两个端点分别为(a1,b1)和(a2,b2),则k1*k2==-1可以转换为(y2-y1)*(b2-b1)==-(x2-x1)*(a2-a1)。类似的,k1==k2转换为(y2-y1)*(a2-a1)==(b2-b1)*(x2-x1)。这样就不用担心斜率不存在的问题。
经过以上详细的分析,我们可以写出如下代码:
[cpp]
#include<iostream>
#include<set>
#include <vector>
using namespace std;
//点结构体
typedef struct P
{
int x,y;//两点坐标
bool operator<(const P& p)const//如果要加入到set中,需要重载<
{
//return this->x<p.x&&this->y<p.y;//注意这样会导致(0,0)<(0,1)且(0,1)<(0,0)的情况
return (this->x<p.x)||((this->x==p.x)&&(this->y<p.y));
}
bool operator==(const P&p)const//重载等于比较
{
return (this->x==p.x)&&(this->y==p.y);
}
};
//线结构体
typedef struct L
{
P p1,p2;//一条线由两点组成
};
//判断两条线的关系,1:垂直 0:平行 -1:其他
int check_two_line(L l1,L l2)
{
int x1=l1.p1.x,y1=l1.p1.y,x2=l1.p2.x,y2=l1.p2.y;
int a1=l2.p1.x,b1=l2.p1.y,a2=l2.p2.x,b2=l2.p2.y;
if((y2-y1)*(b2-b1)==-(x2-x1)*(a2-a1))//斜率公式的变形
return 1;
else if((y2-y1)*(a2-a1)==(b2-b1)*(x2-x1))
return 0;
else
return -1;
}
//判断是否是同一条直线
bool is_same_line(L l1,L l2)
{
if(((l1.p1==l2.p1)&&(l1.p2==l2.p2))||((l1.p1==l2.p2)&&(l1.p2==l2.p1)))//如果线的两个端点都相同,则是同一条直线
return true;
else
return false;
}
//判断能否组成一个矩形
bool is_rect(vector<L> lines)
{
int vertical_num,parallel_num;
for(int i=0;i<4;i++)//每条线都和剩余3条线判断关系
{
vertical_num=0;
parallel_num=0;
for(int j=0;j<4;j++)
{
if(j==i)//不和自己判断
continue;
int rs=check_two_line(lines[i],lines[j]);
if(rs==-1)
return false;
else if(rs==1)
vertical_num++;
else
{
if(is_same_line(lines[i],lines[j]))//如果是平行线还要判断是否是相同的线
return false;
parallel_num++;
}
}
if(!(vertical_num==2&¶llel_num==1))//只有当该线和其他2条线垂直,1条线平行时,才能组成矩形
return false;
}
return true;
}
int main()
{
int T;
cin>>T;
while(T–)
{
set<P> points;
vector<L> lines;
for(int i=0;i<4;i++)
{
P p1,p2;
cin>>p1.x>>p1.y>>p2.x>>p2.y;
points.insert(p1);
points.insert(p2);
L l;
l.p1=p1;
l.p2=p2;
lines.push_back(l);
}
if(points.size()!=4)//能组成矩形的必要条件是有且只有4个点,所以用set来判断更方便
cout<<"NO"<<endl;
else
{
if(is_rect(lines))
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
}
return 0;
}
[/cpp]
代码提交后AC,用时1MS,内存0MB,还不错。
]]>