【凸包 求上凸包路径】I - Problem G. Interstellar Travel  HDU - 6325

I - Problem G. Interstellar Travel  HDU - 6325

After trying hard for many years, Little Q has finally received an astronaut license. To celebrate the fact, he intends to buy himself a spaceship and make an interstellar travel. 
Little Q knows the position of nn planets in space, labeled by 11 to nn. To his surprise, these planets are all coplanar. So to simplify, Little Q put these nnplanets on a plane coordinate system, and calculated the coordinate of each planet (xi,yi)(xi,yi). 
Little Q plans to start his journey at the 11-th planet, and end at the nn-th planet. When he is at the ii-th planet, he can next fly to the jj-th planet only if xi<xjxi<xj, which will cost his spaceship xi×yj−xj×yixi×yj−xj×yi units of energy. Note that this cost can be negative, it means the flight will supply his spaceship. 
Please write a program to help Little Q find the best route with minimum total cost.

Input

The first line of the input contains an integer T(1≤T≤10)T(1≤T≤10), denoting the number of test cases. 
In each test case, there is an integer n(2≤n≤200000)n(2≤n≤200000) in the first line, denoting the number of planets. 
For the next nn lines, each line contains 22 integers xi,yi(0≤xi,yi≤109)xi,yi(0≤xi,yi≤109), denoting the coordinate of the ii-th planet. Note that different planets may have the same coordinate because they are too close to each other. It is guaranteed that y1=yn=0,0=x1<x2,x3,...,xn−1<xny1=yn=0,0=x1<x2,x3,...,xn−1<xn. 

Output

For each test case, print a single line containing several distinct integers p1,p2,...,pm(1≤pi≤n)p1,p2,...,pm(1≤pi≤n), denoting the route you chosen is p1→p2→...→pm−1→pmp1→p2→...→pm−1→pm. Obviously p1p1 should be 11 and pmpm should be nn. You should choose the route with minimum total cost. If there are multiple best routes, please choose the one with the smallest lexicographically. 
A sequence of integers aa is lexicographically smaller than a sequence of bb if there exists such index jj that ai=biai=bi for all i<ji<j, but aj<bjaj<bj. 

Sample Input

1
3
0 0
3 0
4 0

Sample Output

1 2 3

题意:给定平面上n个点,起点横坐标最小,终点横坐标最大。每次可以飞到一个横坐标严格更大的点,代价为两个坐标的叉积。 求起点到终点总代价最小的飞行路线,并输出字典序最小的路线。

思路:因为凸包是求逆时针的时候面积包住所有点的面积最大,那么我们题目这样是顺时针(横坐标越来越大),相反的就是面积最小,所以这题就是求顺时针的凸包,那肯定就包括起点、终点、凸包的拐点了,只要把上凸包求出来就好。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
#define ll long long
struct node
{
    ll x,y;
    int id;
}a[N];
int s[N];
bool cmp(node a,node b)
{
    if(a.x==b.x)
    {
        if(a.y==b.y)
        {
            return a.id<b.id;
        }
       return a.y>b.y;
    }
    else return a.x<b.x;
}
bool judge(int x,int y,int z)
{
    if((a[z].y-a[x].y)*(a[y].x-a[x].x)==(a[z].x-a[x].x)*(a[y].y-a[x].y))
        return a[z].id<a[y].id;
    if((a[z].y-a[x].y)*(a[y].x-a[x].x)>(a[z].x-a[x].x)*(a[y].y-a[x].y))
        return true;
    return false;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld%lld",&a[i].x,&a[i].y);
            a[i].id=i;
        }
        sort(a+1,a+1+n,cmp);
        int top=0;
        s[top++]=1;
        for(int i=2;i<=n;i++)
        {
            if(a[i].x==a[i-1].x&&a[i].y==a[i-1].y)
                continue;
            while(top>=2&&judge(s[top-2],s[top-1],i))
                top--;
            s[top++]=i;
        }
        for(int i=0;i<top;i++)
        {
            if(i)printf(" ");
            printf("%d",a[s[i]].id);
        }
        printf("\n");
    }
    return 0;
}

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 200000 + 10;

struct point
{
	ll x, y;
	int id;
} a[maxn], p[maxn], b[maxn];

int top, n, tot;
int ans[maxn];
bool vis[maxn];

ll cross(point p0, point p1, point p2)//计算叉乘,注意p0,p1,p2的位置,这个决定了方向
{
	return (p1.x - p0.x)*(p2.y - p0.y) - (p1.y - p0.y)*(p2.x - p0.x);
}

ll dis(point a, point b)//计算距离,这个用在了当两个点在一条直线上
{
	return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}

bool cmp(point p1, point 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(point p1, point 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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值