题面
给定平面n个点,求凸包直径(输出凸包直径的平方)
2
<
=
n
<
=
50000
2<=n<=50000
2<=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;
}