【杭电多校round3】G Interstellar Travel
题意
给定二维平面上的n个点,保证只有一个点(P_1)横坐标最小,一个点横坐标最大(P_n),在坐标轴上其余点无序且可能重合
即 y_1=y_n=0, 0=x_1<x_2,x_3,...,x_n−1<x_n
要沿着x严格递增的方式选择一些降落点,从P_1走到P_n
从i点起飞到j点降落,花费的代价是x_i×y_j−x_j×y_i ,
求花费代价最小的方案,输出字典序最小的答案
分析
这题很多人一眼看过去是斜率DP。。。原谅本辣鸡没看不出来。。。但是推式子却推不出来,Claris还是你Claris,迷惑性很强。。。
观察一下上面的代价式子,会发现是一个向量叉积的式子,两两之间选择的点求叉积,联系到三角剖分,可以知道最后的花费代价是所有点连起来的有向面积的和。
如果斜率减小,则获得负面积,所以我们要沿着斜率减小的情况走。也就是维护一个上凸包。
起点,终点,凸包上的拐点必须要选,重合的点只选字典序最小的一个,共线的点,则选择字典序最小的子序列
解决重合的点可以考虑在读入点,排序后就预处理掉
共线的点可以考虑贪心从右往左,如果当前结点序号比前一个选的共线的点还要小,则这个点也选取
(题目要是没看错的话应该可以秒嘞。。。在想复杂了的情况下,思路已经很接近了。。。
代码
#include<bits/stdc++.h>
using namespace std;
struct Point{
long long x,y;
int id;
bool operator < (Point b) {return x<b.x || (x == b.x && y < b.y);}
bool operator == (Point b){return x == b.x && y == b.y;}
long long operator *(Point b){return x*b.y-y*b.x;}
Point operator - (Point b){return Point{x-b.x,y-b.y,0};}
}s[200005],a[200005];
bool book[200005];
int n,m;
int solve()
{
int m = 0,k=1;
s[m++] = a[n];
for(int i = n-1;i>=1;i--)
{
while(m>k && (s[m-1]-s[m-2])*(a[i]-s[m-2])<0) m--;
s[m++] = a[i];
}
m--;book[0] = book[m] = 1;
for(int i = 1;i<m;i++)
if((s[i-1]-s[i])*(s[i]-s[i+1])) book[i] = 1;
int pre;
for(int i = 0;i<m;i++)
if(book[i] == 1) pre = i;
else{
if(s[i].id<s[pre].id)
book[i] = 1, pre = i;
}
putchar('1');
for(int i = m-1;i>=0;i--)
{
if(book[i]) printf(" %d",s[i].id);
}
putchar('\n');
}
int main()
{
int _;scanf("%d",&_);
while(_--)
{
memset(book,0,sizeof(book));
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+n+1);
int now = 1;
for(int i = 2;i<=n;i++)
{
if(a[i] == a[now]){
if(a[i].id < a[now].id) a[now] = a[i];
}
else a[++now] = a[i];
}
n = now;
solve();
}
}