题意
给你n个点,求一个序列p1,p2,..pm,使得其相邻两个点到原点的向量的叉乘和最小,若两个序列答案相同,那么输出字典序最小的序列。
题解
可以很容易的得出,最后围成的形状是一个上凸包,因此我们维护一个上凸包,由于存在重点与三点共线的情况,那么对于重点,我们只取id最小的点就可以了。对于三点共线的话,那么对于这一段共线的点,两端的端点肯定必须要取,对于中间的情况,我们只需要从后往前维护一个递减序列即可。
AC代码
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<stack>
#define N 200005
using namespace std;
typedef long long ll;
vector<ll>ans,gg;
stack<ll>sta;
struct point
{
ll x,y,id;
point(){}
point(ll x,ll y)
{
this->x=x;
this->y=y;
}
}p[N],tubao[N];
ll mark[N];
point operator-(point A,point B)
{
return point(A.x-B.x,A.y-B.y);
}
bool cmp(point a,point b)
{
if(a.x==b.x)
{
if(a.y==b.y)return a.id<b.id;
return a.y<b.y;
}
return a.x<b.x;
}
ll Cross(point A,point B)
{
return A.x*B.y-A.y*B.x;
}
ll ConvexHull(point* p,ll n,point* ch)
{
ll m=0;
for(ll i=0;i<n;i++)
{
if(mark[i])continue;
while(m>=2&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])>0)m--;
ch[m++]=p[i];
}
return m;
}
int main()
{
ll T;
scanf("%lld",&T);
while(T--)
{
memset(mark,0,sizeof(mark));
ans.clear();
ll n;
scanf("%lld",&n);
for(ll i=0;i<n;i++)
scanf("%lld%lld",&p[i].x,&p[i].y),p[i].id=i+1;
sort(p,p+n,cmp);
for(int i=1;i<n;i++)
if(p[i].x==p[i-1].x&&p[i].y==p[i-1].y)
mark[i]=1;
ll tot=ConvexHull(p,n,tubao);
for(ll i=0;i<tot;i++)
{
ans.push_back(tubao[i].id);
while(i<tot-2&&Cross(tubao[i+1]-tubao[i],tubao[i+2]-tubao[i])==0)
gg.push_back(tubao[i+1].id),i++;
ll now=tubao[i+1].id;
for(int j=gg.size()-1;j>=0;j--)
if(gg[j]<now)
sta.push(gg[j]),now=gg[j];
gg.clear();
while(!sta.empty())
{
ans.push_back(sta.top());
sta.pop();
}
}
for(int i=0;i<ans.size()-1;i++)
printf("%lld ",ans[i]);
printf("%lld\n",ans[ans.size()-1]);
}
}