【代码超详解】POJ 3304 Segments(简单计算几何 · 思维 + 跨立实验 + 矢量积)

一、传送门

http://poj.org/problem?id=3304

二、算法分析说明与代码编写指导

题目大意:给一组线段(可能完全重合),问是否存在一条直线,当全部的线段向该直线作正投影时,全部的投影都有重叠部分(至少重叠一个点)。
如果所有线段对某直线的正投影都有公共部分,那么过该直线的垂线肯定与所有线段都相交。
那么,将这条直线绕与其中任意一条线段的交点旋转(顺时针或逆时针均可),直到直线在某一条线段上的交点(率先)移动到该线段的端点时,停止旋转;再将这条直线绕这个线段的端点旋转,直到直线在另一条线段上的交点(率先)移动到该线段的端点,停止旋转。
可见,所有符合要求的直线都可以经过两次旋转,从而与这些线段的两个端点(可能是同一线段的两个端点)重合。
所以,我们只需要用二重循环,枚举在这些线段中任选的两个端点,考察过这两个端点的直线是否与其它线段都相交。
判断直线与线段的相交,用向量法。取直线上的一点,用直线的方向向量分别去叉乘该点到线段的两个端点这两个向量,如果矢量积的模同为正或同为负,那么它们在该直线的同侧;如果矢量积的模同为零,那么线段与直线重合。
枚举端点并连直线时,如果两个端点重合,那么直线可以是沿任意方向的。既然沿任意方向都可以,那么到底选哪一条直线,可以交由接下来的判断决定,因为如果过这两个重合端点的直线符合要求,则在枚举剩下的情况时肯定会被枚举出来,所以在两个端点重合时跳过判断就可以了。
常数优化:
1、判断距离时不要开根。按题目要求,距离小于 1e8 的时候认为点重合,可以转化为距离的平方小于 1e16 的时候认为点重合。开根号需要的时间比较长,因此能避免就避免。
2、用两个指针 S 和 T 代替循环中的 s[i] 。因为下标访问运算符 [] 需要计算内存的偏移。
3、计算矢量积的时候,直线的方向向量不变,这部分作为运算的中间结果可以先算出来,就无需向一些答案那样每次都在计算矢量积时重新计算方向向量。

三、AC 代码

19 行与 20 行我不知道是什么问题,注释 19 行用 20 行就 AC,否则 WA。如果大家知道原因,可以给我留言。

#include<cstdio>
#include<cmath>
#pragma warning(disable:4996)
struct point { double x, y; }; struct segment { point s, t; };
const unsigned nmax = 100; const double e = 1e-8, e2 = 1e-16;
unsigned t, n; segment s[nmax], * S, * T; bool ok;
inline int sgn(const double& x) {
	if (fabs(x) < e)return 0;
	return x < 0 ? -1 : 1;
}
inline double dist2(const point& a, const point& b) { return pow(a.x - b.x, 2) + pow(a.y - b.y, 2); }
inline double cross_product(const double& x1, const double& y1, const double& x2, const double& y2) {
	return x1 * y2 - x2 * y1;
}
inline bool chk(const point& a, const point& b) {
	if (dist2(a, b) < e2)return false;
	double X0 = b.x - a.x, Y0 = b.y - a.y, X1, Y1, X2, Y2;
	for (unsigned i = 0; i < n; ++i) {
		//S = s + i; X1 = S->s.x - a.x; Y1 = S->s.y - a.y; X2 = S->t.x - a.x; Y2 = S->t.y - a.y;
		X1 = s[i].s.x - a.x; Y1 = s[i].s.y - a.y; X2 = s[i].t.x - a.x; Y2 = s[i].t.y - a.y;
		if (sgn(cross_product(X0, Y0, X1, Y1)) * sgn(cross_product(X0, Y0, X2, Y2)) > 0)return false;
	}
	return true;
}
int main() {
	scanf("%u", &t); ++t;
	while (--t) {
		scanf("%u", &n); ok = false;
		for (unsigned i = 0; i < n; ++i) {
			S = s + i; scanf("%lf%lf%lf%lf", &S->s.x, &S->s.y, &S->t.x, &S->t.y);
		}
		if (n == 1) { puts("Yes!"); continue; }
		for (unsigned i = 0; i < n; ++i) {
			for (unsigned j = i + 1; j < n; ++j) {
				S = s + i; T = s + j;
				if (chk(S->s, T->s) || chk(S->s, T->t) || chk(S->t, T->s) || chk(S->t, T->t)) { ok = 1; break; }
			}
		}
		if (ok)puts("Yes!"); else puts("No!");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值