UVA1347 旅行 Tour(神仙dp状态!)

做这题犯了好多 nt 的事,在朋友圈发完牢骚了!

记录一下这题的神仙状态设计吧qwq。

讲一下我的心路历程:

一开始:回路?搜索?不可能吧,这是蓝题啊(

经过思考:嗯这大概貌似是个 dp 吧,好像 LIS 的做法?变成两个人的相遇问题。但是很快我发现写不出来。

后来,经过某书的悉心指导,终于体悟了这道题对于无后效的处理。

首先我们考虑为什么能变成相遇问题,去时向右单调,回程向左单调,回程正走反走显然是等价。

接下来进入最精彩的部分。

状态

思考一下类比 LIS 的做法为什么不行,你会发现这个状态的缺陷在于你不知道是否选了一样的点,可能走到了当前的 $i,j$ 时你的 $i$ 和 $j$ 都从 $j-1$ 走了过来,这是不行的。

所以我们要设计一个状态,巧妙的避开这个问题:$dp(i,j)$ 代表从 $1$ 到 $\max{i,j}$ 全部恰好被选过一次,且当前两个人分别在 $i,j$ 两点,最少还需要再走多远。好神仙的状态!但还没完!我们可以继续优化,因为这两个人是对称的,所以 $dp(i,j) = dp(j,i)$ 所以我们可以直接令 $dp(i,j),i > j$ 这样就更方便了一些。

一个好的状态是很容易设计出转移方程的,对于 $i,j$ 两个人,显然只能从 $i+1$ 这个点走过来(根据是我们的状态),所以 $i+1$ 要么走到了 $i$,要么走到了 $j$,所以转移方程就是 $dp(i,j) = \min{dp(i+1,j)+dis(i,i+1),dp(i+1,i)+dis(j,i+1)}$

边界就是在 $i = n - 1$ 的时候,因为二者的相遇点必然是 $n$,且根据状态,除了 $n$ 所有的点我们都走过了,所以边界就是 $dp(n-1,j) = dis(n-1,n) + dis(j,n)$ 让他们两个各走一步,在 $n$ 相遇。

答案呢就是 $dp(2,1) + dis(1,2)$ 没毛病啦。

#include<cstdio>
#include<queue>
#include<cmath>
#include<algorithm>
#include<cstring>
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i<(b);++i)
#define rrep(i,a,b) for(register int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
    x=0;char ch=getchar();bool f=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const int N = 1005;
struct qwq{
    double x,y;    
}a[N];
double dp[N][N];
int n;
double inf;
inline double po(double x){return x * x;}
inline double dis(qwq i,qwq j){
    return     sqrt(po(i.x - j.x) + po(i.y - j.y));
}
inline double get(int i,int j){return dis(a[i],a[j]);}
double f(int i,int j){
    if(dp[i][j] != inf)return dp[i][j];
    if(i == n - 1)return dp[i][j] = (get(n-1,n) + get(j,n));
    double x = f(i+1,j) + get(i,i+1);
    double y = f(i+1,i) + get(j,i+1);
    return dp[i][j] = min(x,y);    
}
signed main(){

    while(scanf("%d",&n) != EOF){
        memset(dp,120,sizeof(dp));//一个清double正无穷的好方法 
        inf = dp[0][0];
        rep(i,1,n){
            scanf("%lf %lf",&a[i].x,&a[i].y);    
        }
        printf("%.2lf\n",f(2,1)+get(2,1));
    }
}

 

posted @ 2022-05-11 21:53  Xu_brezza  阅读(46)  评论(0编辑  收藏  举报