性质
凸包是将平面所有点都围起来的多边形方案中,面积最小的,同时也是周长最小的。
Andrew 算法
分析
预处理:
- 先将所有点排序:x 为第一关键字,y 为第二关键字
求解:
- 从左至右维护凸包上半部分的点
- 从右至左维护凸包下半部分的点
下一个点若在栈顶边的左侧,则弹出栈顶的边;若下一个点在栈顶边的右侧,则保留栈顶的边,将新的边压入栈中;若下一个点在栈顶边的延长线上,按照题意,弹出栈顶的边,将新的边压入栈中。
最后记得将两个边界点更新一下栈。
程序代码
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
#include <cstring>
#include <stack>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 110;
int m, n;
PII q[N];
int stk[N]; // 用数组模拟栈
bool used[N];
// 重载减号:对应坐标相减
PII operator- (PII a, PII b)
{
return {a.x - b.x, a.y - b.y};
}
// 点积
int cross(PII a, PII b)
{
return a.x * b.y - a.y * b.x;
}
// 从向量角度计算a-b-c围成的三角形面积
int area(PII a, PII b, PII c)
{
return cross(b - a, c - a);
}
void andrew()
{
sort(q, q + n);
int top = 0; // 栈顶,从1开始存,0表示为空
memset(used, false, sizeof(used));
// 从左往右遍历所有点,得到上半部分的凸边
for(int i = 0; i < n; i++) {
// 大于0:凹下去
// 等于0:共线
while( top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) >= 0 ) {
// 删除操作
used[stk[top--]] = false;
}
stk[++top] = i;
used[i] = true;
}
// 边界点也要更新
used[0] = false;
// 从右往左遍历所有点,得到下半部分的凸边
for(int i = n - 1; i >= 0; i--) {
// 构成上半部分的凸边不需要考虑
if( used[i] ) continue;
while( top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) >= 0 ) {
top--;
}
stk[++top] = i;
}
// 此时栈里存储了凸包的所有点,其中起点被存了两次
// 逆时针输出凸包的所有点
for(int i = top; i > 1; i--) {
cout << q[stk[i]].x << " " << q[stk[i]].y << endl;
}
}
int main()
{
cin >> m;
while( m-- ) {
cin >> n;
for(int i = 0; i < n; i++) {
cin >> q[i].x >> q[i].y;
}
andrew();
}
return 0;
}