POJ 1228 Grandpa's Estate 计算凸包+判断点在线段上

题目链接:http://poj.org/problem?id=1228

题目大意:给定n个二维坐标系中的点,这些点是一个凸多边形顶点的子集。判断这些点是否能唯一确定原来的凸多边形。

解题思路:
考虑多边形的一条边。对于给定两个端点,连起来变成一条边。原来的多边形中可能在这条边之外还有一个端点p',那么这条边在原多边形中可能就不存在。 那么怎么能确定一条边一定在原多边形中存在呢?

如果在一条线段上除了端点p1 p2之外有其他的点p3,那么这条边在原多边形中一定存在。因为不可能添加一个顶点使得这条线段仍然是凸的。p3限制了线段p1p2向外凸出。所以只需要先对点集求个凸包,然后判断凸包的每条边上是否都有除了端点之外的第三个点,使得这条边确定下来。如果凸包中的每条边都满足这个条件,便可唯一确定原来的凸多边形。

另外这道题也有需要特判的情况,以上的分析都基于能求出一个多边形凸包,实际上这样的凸包可能并不存在。我使用的是xy坐标排序的graham-scan算法,如果所有的点都共线的话,算法依然会求出一个点集(包括了输入的所有的点),并且可能判断出点集中每条的线段上都有第三个顶点,然后输出YES。实际上很显然,直线外可能存在其他的顶点把这条线“撑开”,答案应该是NO。所以必须特判所有顶点共线的情况。按照惯例,考虑n的极限情况,n=1时,只有一个顶点。这时候判断共线的函数会把这种情况判断成共线,所以n=1时也没有问题。

至于复杂度: 求凸包O(nlogn),判断唯一性时枚举每条边和每个点,O(nh),其中h是凸包中点的数目。所以总复杂度在O(nlogn)和O(nh)之间。

/*
 *  2014.11.8
 *  Problem: 1228	
 *  Memory: 204K		
 *  Time: 16MS
 */
#include "stdio.h"
#include "math.h"
#define EPS 1e-8
#define MAXN 2007

struct Point {
	double x, y;
}p[MAXN], c[MAXN];

int n, top;

void sort(int l, int r) {
	int i=l, j=r;
	double x = p[(l+r)>>1].x, y = p[(l+r)>>1].y;
	do {
		while (p[i].x<x || (p[i].x==x && p[i].y<y)) i++;
		while (p[j].x>x || (p[j].x==x && p[j].y>y)) j--;
		if (i<=j) {
			Point t = p[i]; p[i] = p[j]; p[j] = t;
			i++; j--;
		}
	} while (i<=j);
	if (i<r) sort(i, r);
	if (l<j) sort(l, j);
}

double max(double a, double b) {return a>b?a:b;}
double min(double a, double b) {return a>b?b:a;}

// 叉积
double cross(Point a, Point b, Point c) {
	double x1 = b.x-a.x, y1 = b.y-a.y;
	double x2 = c.x-a.x, y2 = c.y-a.y;
	return x1*y2-x2*y1;
}

// 判断是否左转, 包括共线情况
bool turnLeft(Point a, Point b, Point c) {
	return cross(a, b, c)>=-EPS;
}

// 求凸包
void convexHull() {
	sort(1, n);

	top = 1; c[top] = p[1];
	for (int i=2;i<=n;i++) {
		while (top>=2 && turnLeft(c[top-1], c[top], p[i])) top--;
		c[++top] = p[i];
	}
	int pn = top;
	for (int i=n-1;i>=1;i--) {
		while (top>=pn+1 && turnLeft(c[top-1], c[top], p[i])) top--;
		c[++top] = p[i];
	}
	top --;
}

// 判断点是否在线段上,不包括端点
bool onSegment(Point a, Point b, Point c) {
	if (fabs(cross(a, b, c))<EPS && 
	    min(a.x,b.x)<=c.x && c.x<=max(a.x,b.x) &&
	    min(a.y,b.y)<=c.y && c.y<=max(a.y,b.y) &&
		!((c.x==a.x&&c.y==a.y)||(c.x==b.x&&c.y==b.y)))
	    return true;
	return false;
}

// 验证凸包中的每一条边是否确定
bool verify() {
	c[top+1] = c[1];
	for (int i=1;i<=top;i++) {
		bool on = false;
		for (int j=1;j<=n;j++)
			if (onSegment(c[i], c[i+1], p[j])) {
				on = true;
				break;
			}

		if (!on) return false; 
	}
	return true;
}

// 判断所有点是否共线
bool collinear() {
	p[n+1] = p[1];
	p[n+2] = p[2];
	for (int i=1;i<=n;i++) 
	if (fabs(cross(p[i],p[i+1],p[i+2]))>EPS) return false;
	return true;
}

int main() {
	
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d", &n);
		
		for (int i=1;i<=n;i++) scanf("%lf %lf", &p[i].x, &p[i].y);

		// 判断顶点是否共线,如果共线直接输出NO
		if (collinear()) {
			printf("NO\n");
			continue;
		}

		// 求凸包, 凸包上的点保存在 c 数组中
		convexHull();

		// 判断凸包上的点是否能唯一确定一个凸多边形
		// 即判断凸包每条线段上都有除了端点外的点
		bool only = verify();

		if (only) {
			printf("YES\n");
		} else {
			printf("NO\n");
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值