题解 P9119【[春季测试 2023] 圣诞树】

$\text{Link}$

题意

给你一个平面上的 $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();
}
posted @ 2023-03-07 13:47  ffffyc  阅读(18)  评论(0)    收藏  举报  来源