P1452 旋转卡壳

偶尔善良 提交于 2020-02-02 04:13:10

题面

给定平面n个点,求凸包直径(输出凸包直径的平方)
2<=n<=500002<=n<=50000
(实测存在一列点构成直线的数据)

分析

凸包直径:凸包两两顶点间最远的距离
容易想到O(n2)的方法,求出凸包后遍历顶点对求距离max即可

但有更优的做法,遍历的一个点移动一次,其对面的点(对踵点)移动的幅度并不会太大,并且旋转方向相同(同顺时针或同逆时针),所以其实存在O(N)的做法。

这种遍历一般有两种:点-点遍历,边-点遍历:
在这里插入图片描述
第二种在代码上实现更容易(第一种貌似有特例,如特别扁的图形,会失去长度的单峰性)

过程即start边在凸包上移动,分别找出每个start边的最远opposite点。这个最远可以用三角形面积来衡量,如图上的那样,根据平行线的知识,过其他顶点作start边的平行线,最远的平行线可以使三角形面积最大,也就是最远的点。这时计算这个最远点与start两端点的距离取max即得出当前start边下能得到的最大直径。对所有start边下的直径取max即是全局最大直径。

如何快速找出对每一个start最远的opposite?如果单纯遍历仍然会落入O(n2)。而通过观察同一start下三角形的面积是一个单峰函数(平行线从近到远再到近),并且已知start移动一次,这个峰移动不会太大,而且是同向移动,联想到可以把这次start求出的最大opposite位置作为下次start找极大值的起点,这样可以较快遍历到峰上。
在这里插入图片描述
图即start1发现峰在opposite1后,start2从opposite2开始同向遍历,到峰停止。
判断峰可用前后面积比较得出,递增则迭代,开始减小则停止
三角形面积用向量外积完成(外积得到平行四边形面积,一半为三角形面积)

对于共线的数据,在求凸壳时保证共线的数据只会加入共线数据的两端即可

代码关键部分就得出了

int rotateCalipers()//求直径
{
	int ans = -1;
	int size = hull.size();//hull是凸壳上的点序列
	int op = 1;//对面的点,逆时针沿凸包移动
	point p1, p2;//两个点(结构体封装xy)表示start边
	for (int i = 0; i < size; i++)//现在的边是i和i+1构成的
	{
		p1 = hull[i];p2 = hull[(i + 1) % size];//Vect是向量结构体,用两个Point构造,cross是外积
		while (Vect(hull[(op + 1) % size], p1).cross(Vect(hull[(op + 1) % size], p2)) > Vect(hull[op], p1).cross(Vect(hull[op], p2)))//用外积算平行四边形面积(一半即三角形面积)
			op = (op + 1) % size;//op继续移动
		ans = max(ans, max(dis(p1, hull[op]), dis(p2, hull[op])));//更新最远距离
	}
	return ans;
}

完整代码

#include<stdio.h>
#include<cstdlib>
#include<iostream>
#include<math.h>
#include<vector>
#include<algorithm>
using namespace std;
struct point
{
	point() {}
	point(int a, int b) :x(a), y(b) {}
	int x, y;
	bool operator <(const point& other)//为了sort
	{
		if (x==other.x) {//x1=x2
			if (y < other.y)return 1;
			else return 0;
		}
		else
		{
			if (x < other.x)return 1;
			else return 0;
		}
	}
	bool operator == (const point& other)//为了unique
	{
		return x-other.x==0 && y - other.y == 0;
	}
};
struct Vect//向量
{
	int x, y;
	Vect(point& p1, point& p2) :x(p2.x - p1.x), y(p2.y - p1.y){}//从点构造向量
	int cross(Vect other)//叉积
	{
		return x * other.y - other.x * y;
	}
};
vector<point> v;//存所有点,并进行从小到大排序
int line[50005],ptr=0;//保存半个凸壳路径
vector<point> hull;//保存完整凸壳路径
void convex()//求凸壳
{
	int size = v.size();
	for (int i = 0; i < size; i++)//处理i
	{
		while (ptr > 1 && Vect(v[line[ptr-1]],v[i]).cross(Vect(v[line[ptr-2]],v[line[ptr-1]]))>=0)
			//求新加入的点连线 与 原本延伸方向的叉积。根据右手定则,如果是正的,则在原本延伸方向的外侧
			//等于号保证不能共线
			ptr--;//后退,直到能与v[i] 形成凸壳
		line[ptr++] = i;//符合,入凸包(用栈结构储存)
	}
	for (int i = 0; i < ptr; i++)
	{
		hull.push_back(v[line[i]]);
	}
	hull.erase(--hull.end());
}
int dis(point& a, point& b)//距离平方
{
	return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
int rotateCalipers()
{
	int ans = -1;
	int size = hull.size();
	int op = 1;//对面的点,逆时针沿凸包移动
	point p1, p2;
	for (int i = 0; i < size; i++)//现在的边是i和i+1构成的
	{
		p1 = hull[i];p2 = hull[(i + 1) % size];
		while (Vect(hull[(op + 1) % size], p1).cross(Vect(hull[(op + 1) % size], p2)) > 
		Vect(hull[op], p1).cross(Vect(hull[op], p2)))//用外积算平行四边形面积(一半即三角形面积)
			op = (op + 1) % size;
		ans = max(ans, max(dis(p1, hull[op]), dis(p2, hull[op])));//更新最远距离
	}
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	int n;
	int x, y;
	cin >> n;
	point temp;
	for (int i = 0; i < n; i++)
	{
		cin >> x >> y;
		temp = point(x, y);
		v.push_back(temp);
	}
	sort(v.begin(), v.end());
	v.resize(unique(v.begin(), v.end()) - v.begin());//去重点
	double ans = 0;
	convex();
	ptr = 0;
	reverse(v.begin(), v.end());//反转,等效于反向求凸壳
	convex();
	//已经构建完成完整凸壳
	cout << rotateCalipers();
	return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!