题解 P9119【[春季测试 2023] 圣诞树】
题意
给你一个平面上的 $n$ 个点的凸包,求 TSP 路线,即从最高点出发并遍历所有点的最短的路径。
$n\le 1000$。
思路
结论:路线不会交叉。
证明:因为凸四边形对角线长度和一定大于一组对边的长度和,所以如果 $(i,j)$ 与 $(a,b)$ 交叉,我们将这两条分别更换为 $(i,a)$ 和 $(j,b)$ 答案一定变小。
对于随意一条路径,我们使用调整法,每次随便选一组交叉的边调整至不交叉,一定可以调整到整条路径不交叉。因为每次调整都会使路径长度变短,所以调整次数有限并不会调整回之前的状态。
我们从最高点开始按顺时针给每个点重新编号,为了方便,$0$ 号和 $n$ 号点都是最高点。由于路线不会交叉,所以时时刻刻没有被遍历过的结点的新编号都是一段连续的区间。
考虑设计 dp,令 $f_{l,r,0/1}$ 表示 $[l+1,r-1]$ 还没有被遍历,上一个遍历的结点是 $l/r$ 时的最短路径。
初始状态 $f_{0,n,0}=f_{0,n,1}=0$,一次转移就是 $f_{l,r,0}=\min(f_{l-1,r,0}+\text{dis}(l-1,l),f_{l-1,r,1}+\text{dis}(l,r))$,$f_{l,r,1}=\min(f_{l,r+1,0}+\text{dis}(l,r),f_{l,r+1,1}+\text{dis}(r,r+1))$。
要输出方案就记录一下决策就好了。
时间复杂度 $O(n^2)$。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
namespace IO{//by cyffff
}
const int N=1e3+10;
const ld INF=1e18;
int n,k,g[N][N][2],p[N];
ld px[N],py[N],f[N][N][2];
inline ld sqr(ld x){
return x*x;
}
inline ld dis(int x,int y){
return sqrt(sqr(px[p[x]]-px[p[y]])+sqr(py[p[x]]-py[p[y]]));
}
inline void answer(int l,int r,int t){
if(l<0||r>n) return ;
if(t==0){
if(g[l][r][0]) answer(l-1,r,1);
else answer(l-1,r,0);
}else{
if(g[l][r][1]) answer(l,r+1,1);
else answer(l,r+1,0);
}
write(t?p[r]:p[l]),putc(' ');
}
int main(){
scanf("%d",&n);
ld tmp=-INF;
for(int i=1;i<=n;i++){
scanf("%Lf %Lf",px+i,py+i);
if(py[i]>tmp) tmp=py[i],k=i;
}
for(int i=k;i<=n;i++)
p[i-k]=i;
for(int i=1;i<=k;i++)
p[i+n-k]=i;
for(int l=0;l<=n;l++)
for(int r=l;r<=n;r++)
f[l][r][0]=f[l][r][1]=INF;
f[0][n][0]=f[0][n][1]=0;
for(int l=0;l<=n;l++)
for(int r=n;r>l+1;r--){
ld LL=f[l][r][0]+dis(l,l+1),LR=f[l][r][1]+dis(r,l+1);
if(LL<LR) f[l+1][r][0]=LL;
else f[l+1][r][0]=LR,g[l+1][r][0]=1;
ld RL=f[l][r][0]+dis(l,r-1),RR=f[l][r][1]+dis(r,r-1);
if(RL<RR) f[l][r-1][1]=RL;
else f[l][r-1][1]=RR,g[l][r-1][1]=1;
}
ld ans=INF;
int pos=0,qwq=0;
for(int i=0;i<n;i++)
for(int j=0;j<=1;j++){
if(f[i][i+1][j]<ans){
ans=f[i][i+1][j];
pos=i,qwq=j;
}
}
answer(pos,pos+1,qwq);
flush();
}

浙公网安备 33010602011771号