[好题][枚举][叉积]Segments POJ3304

Given n segments in the two dimensional space, write a program, which determines if there exists a line such that after projecting these segments on it, all projected segments have at least one point in common.

Input

Input begins with a number T showing the number of test cases and then, T test cases follow. Each test case begins with a line containing a positive integer n ≤ 100 showing the number of segments. After that, n lines containing four real numbers xyxy2 follow, in which (x1, y1) and (x2, y2) are the coordinates of the two endpoints for one of the segments.

Output

For each test case, your program must output "Yes!", if a line with desired property exists and must output "No!" otherwise. You must assume that two floating point numbers a and b are equal if |a - b| < 10-8.

Sample Input

3
2
1.0 2.0 3.0 4.0
4.0 5.0 6.0 7.0
3
0.0 0.0 0.0 1.0
0.0 1.0 0.0 2.0
1.0 1.0 2.0 1.0
3
0.0 0.0 0.0 1.0
0.0 2.0 0.0 3.0
1.0 1.0 2.0 1.0

Sample Output

Yes!
Yes!
No!

题意: 给出n条线段,判断是否存在一条直线,使得这n条线段在直线上的投影存在一个公共点。

分析: 直接正着做并不好想,假设已知一条直线L使得n条线段投影在直线上存在某个公共点P,此时过P点作垂线一定与这n条线段相交,因此题目就转化为判断是否存在一条直线与已知n条线段相交,如果存在这样一条直线,作它的垂线就是题目要求直线。

那具体如何去找这样的一条直线呢?可以证明,如果这样的直线构成的集合非空,那么集合中一定存在一条直线过图中存在的两端点,因此只需要枚举任意两端点,用这两端点代表直线,只要存在一条直线穿过所有线段就输出"Yes!",如果一条也没有就输出"No!"。具体的证明为,首先从符合要求的直线集合中任取一条直线,将其水平平移,平移至遇到第一个端点,之后将直线旋转,旋转至遇到第二个端点,显然集合中任意的直线都可以这样操作。

下面问题就变成了如果判断某直线穿过所有的线段,这个问题可以用叉积解决,如果对于任一条线段,其两端点在直线同侧,那么直线一定不过这条线段,对应条件为叉积同正或者同负。

最后注意有两个坑。可能需要特判n=1或者n=2的情况。如果枚举点时两点距离小于1e-8,那说明这两点重合了,不能以这两点作为直线,所以要跳过。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
const double eps = 1e-8;
const double inf = 1e20;
const double pi = acos(-1.0);
using namespace std;

int n;

int sgn(double x)
{
	if(fabs(x) < eps)return 0;
	if(x < 0)return -1;
	else return 1;
}

struct Point
{
	double x, y;
	Point(){}
	Point(double _x,double _y){x = _x, y = _y;}
	void input(){scanf("%lf%lf",&x,&y);}
	void output(){printf("%.2f %.2f\n",x,y);}
	bool operator == (Point b)const{return sgn(x-b.x) == 0 && sgn(y-b.y) == 0;}
	bool operator < (Point b)const{return sgn(x-b.x)== 0?sgn(y-b.y)<0:x<b.x;}
	Point operator -(const Point &b)const{return Point(x-b.x,y-b.y);}
	//叉积
	double operator ^(const Point &b)const{return x*b.y - y*b.x;}
	//点积
	double operator *(const Point &b)const{return x*b.x + y*b.y;}
	//返回长度
	double len(){return hypot(x,y);/*库函数*/}
	//返回长度的平方
	double len2(){return x*x + y*y;}
	//返回两点的距离
	double distance(Point p){return hypot(x-p.x,y-p.y);}
	Point operator +(const Point &b)const{return Point(x+b.x,y+b.y);}
	Point operator *(const double &k)const{return Point(x*k,y*k);}
	Point operator /(const double &k)const{return Point(x/k,y/k);}
	//`计算pa  和  pb 的夹角`
	//`就是求这个点看a,b 所成的夹角`
	//`测试 LightOJ1203`
	double rad(Point a,Point b)
	{
		Point p = *this;
		return fabs(atan2( fabs((a-p)^(b-p)),(a-p)*(b-p) ));
	}
	//`化为长度为r的向量`
	Point trunc(double r)
	{
		double l = len();
		if(!sgn(l))return *this;
		r /= l;
		return Point(x*r,y*r);
	}
	//`逆时针旋转90度`
	Point rotleft(){return Point(-y,x);}
	//`顺时针旋转90度`
	Point rotright(){return Point(y,-x);}
	//`绕着p点逆时针旋转angle`
	Point rotate(Point p,double angle)
	{
		Point v = (*this) - p;
		double c = cos(angle), s = sin(angle);
		return Point(p.x + v.x*c - v.y*s,p.y + v.x*s + v.y*c);
	}
}p[205];

struct Line
{
	Point s, e;
	Line(){}
	Line(Point _s,Point _e)
	{
		s = _s;
		e = _e;
	}
}l[105];

bool check()
{
	for(int i = 1; i <= 2*n; i++)//枚举任意两点 
		for(int j = i+1; j <= 2*n; j++)
		{
			if(!sgn(p[i].x-p[j].x) && !sgn(p[i].y-p[j].y))
				continue;
			bool flag = false;
			for(int k = 1; k <= n; k++)//枚举任意线段 
			{
				Point t1(l[k].s.x-p[i].x, l[k].s.y-p[i].y);
				Point t2(l[k].e.x-p[i].x, l[k].e.y-p[i].y);
				Point t(p[j].x-p[i].x, p[j].y-p[i].y);
				int a = sgn(t1^t), b = sgn(t2^t);
				if(min(a, b) == 1 && max(a, b) == 1 || min(a, b) == -1 && max(a, b) == -1)
				{
					flag = true;
					break;
				}
			}
			if(!flag)
				return true;
		}
	return false;
}

signed main()
{
	int T;
	cin >> T;
	while(T--)
	{
		scanf("%d", &n);
		for(int i = 1; i <= n; i++)
		{
			l[i].s.input();
			l[i].e.input();
			p[i] = l[i].s;
			p[i+n] = l[i].e;
		}
		if(check())
			puts("Yes!");
		else
			puts("No!");
	} 
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值