题目:Problem G. Interstellar Travel
官方题解:
显然坐标相同的点里只保留编号最小的点最优。
将起点到终点的路径补全为终点往下走到无穷远处,再往左走到起点正下方,再往上回到起点。任意路径中回到起点部分的代价相同,观察代价和的几何意义,就是走过部分的面积的相反数。代价和最小等价于面积最大,故一定是沿着上凸壳行走。
显然起点、终点、凸壳的拐点必须要作为降落点。对于共线的点a1,a2,...,am,若一个点ii的编号是[i,m]中最小的,那么在此处降落可以最小化字典序。
时间复杂度O(nlog n)。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <set>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX = 200000 + 10;
struct node
{
ll x, y;
int id;
} a[MAX], p[MAX], b[MAX];
int top, n, tot;
int ans[MAX];
bool vis[MAX];
ll cross(node p0, node p1, node p2)//计算叉乘,注意p0,p1,p2的位置,这个决定了方向
{
return (p1.x - p0.x)*(p2.y - p0.y) - (p1.y - p0.y)*(p2.x - p0.x);
}
ll dis(node a, node b)//计算距离,这个用在了当两个点在一条直线上
{
return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}
bool cmp(node p1, node p2)//去重
{
if (p1.x == p2.x)
{
if (p1.y == p2.y)
return p1.id<p2.id;
return p1.y<p2.y;
}
return p1.x<p2.x;
}
bool cmp2(node p1, node p2)//极角排序
{
ll z = cross(a[0], p1, p2);
if (z>0 || (z == 0 && dis(a[0], p1)<dis(a[0], p2)))
return 1;
return 0;
}
void Graham()
{
sort(a + 1, a + tot, cmp2);
top = 1;
p[0] = a[0];
p[1] = a[1];
for (int i = 2; i<tot; i++)
{
while (cross(p[top - 1], p[top], a[i])<0 && top)
top--;
top++;
p[top] = a[i];
}
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
memset(vis, false, sizeof(vis));
scanf("%d", &n);
int flag = 0;
for (int i = 0; i<n; i++)
{
scanf("%lld%lld", &b[i].x, &b[i].y);
b[i].id = i + 1;
if (b[i].y != 0)
flag = 1;
}
//去重
sort(b + 1, b + n, cmp);
a[0] = b[0];
tot = 1;
for (int i = 1; i<n; i++)
{
if (b[i].x == b[i - 1].x&&b[i].y == b[i - 1].y)
{
continue;
}
else
{
a[tot++] = b[i];
}
}
//计算凸包
Graham();
//特判全在x轴上的情况
if (flag == 0)
{
int cnt = 0;
ans[++cnt] = p[top].id;
for (int i = top - 1; i >= 1; i--)
{
if (p[i].id<ans[cnt])
ans[++cnt] = p[i].id;
}
ans[++cnt] = p[0].id;
printf("%d", ans[cnt]);
for (int i = cnt - 1; i >= 1; i--)
{
printf(" %d", ans[i]);
}
printf("\n");
continue;
}
//把共线但没包含在凸包p[]中的点加进来
//这些点只会出现在p[0]和p[top]的连线上
int now = top;
int nowid = p[top].id;
for (int i = tot - 1; i>0; i--)
{
ll z = cross(p[now], p[0], a[i]);
if (z == 0 && a[i].id != nowid) {
p[++top] = a[i];
}
}
//记录拐点
sort(p, p + top + 1, cmp);
for (int i = 1; i < top; i++)
{
if (cross(p[i - 1], p[i], p[i + 1]) != 0)
vis[i] = true;
}
int cnt = 0;
ans[++cnt] = p[top].id;
for (int i = top - 1; i >= 0; i--)
{
if (vis[i] == true)
{
ans[++cnt] = p[i].id;
continue;
}
if (p[i].id < ans[cnt])
ans[++cnt] = p[i].id;
}
printf("%d", ans[cnt]);
for (int i = cnt - 1; i >= 1; i--)
{
printf(" %d", ans[i]);
}
printf("\n");
}
return 0;
}