旅行uva1347应用于DAG上的dp

题意翻译
题目大意:

John Doe想用最小的路程游览完所有目的地。每个目的地都用坐标xi,yi表示。任何两目的地的xi都不相同。两目的地之间的路程是两点之间的直线距离。John是这样走的:他从最左边的点开始,然后只能向右走,走到最右边的点,然后他只能向左走,回到最开始的点。每个点都要走到,并且除了出发点以外每个点只能经过一次。

请写出一个程序求符合要求的最小路程。

(输入)

每组数据有很多组。每组第一行的数是这一组的点数。接下来每一行代表一个点,左边是xi,右边是yi。这些行表示的点的xi是升序的。空格会在输入中随意出现。输入数据是正确的。

(输出)

每组数据的答案占一行,答案在小数点后保留两位。

(样例输入解释)

第一组有3个点。第一个点xi是1,yi是1。第二个点xi是2,yi是3。第三个点xi是3,yi是1。
本苟蒻ac代码

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
int maxn=1000+10;
double dist[maxn][maxn]={0};
double d[maxn][maxn]={0};
class node{
	public:
	int x;
	int y;
};
double distance(int i,int j,vector<node> m)
{
	if(dist[i][j]>0) return dist[i][j];
	if(dist[j][i]>0) return dist[j][i]; 
	return sqrt((m[i].x-m[j].x)*(m[i].x-m[j].x)+(m[i].y-m[j].y)*(m[i].y-m[j].y));
}

double solve(int i,int j,vector<node> m,int n)
{
	double& ans=d[i][j];
	if(ans>0) return ans;
	if(i==n-1) return ans=dist[n-1][n]+dist[j][n];
	ans=min((solve(i+1,j,m,n)+dist[i][i+1]),(solve(i+1,i,m,n)+dist[j][i+1])); 
	return ans;
}
int main(void)
{
	int n;
	node xp;
	node coushu;
	coushu.x=coushu.y=-1;
	vector<node> m;
	while(cin>>n)
	{
	m.clear();
	memset(dist,0,sizeof(dist));
	memset(d,0,sizeof(d));
	m.push_back(coushu);
	for(int i=0;i<n;i++)
	{
		cin>>xp.x>>xp.y;
		m.push_back(xp);
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			dist[i][j]=distance(i,j,m);
		}
	}
	solve(1,1,m,n);
	printf("%.2f\n",d[1][1]);
}
	return 0;
} 

某大佬ac代码

#include<iostream> 
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn=1000+5;

int n;

struct node
{
    int x, y;
}a[maxn];

double d[maxn][maxn];   //第一个走到i,第二个人走到j,d[i][j]表示此时还需要走多长的距离


double dist(int i,int j)
{
    int dx = a[i].x - a[j].x;
    int dy = a[i].y - a[j].y;
    return hypot(dx, dy);  //计算直角三角形的斜边
}

double dp(int i, int j)  //i一定大于j
{
    double& ans = d[i][j];
    if (ans > 0)  return ans;
    if (i == n - 1)
        return ans=dist(i, n) + dist(j, n);
    ans = min(dp(i + 1, j) + dist(i + 1, i), dp(i + 1, i) + dist(i + 1, j));
    return ans;
}

int main()
{
    //freopen("D:\\txt.txt", "r", stdin);
    while (cin >> n && n)
    {
        memset(d, 0, sizeof(d));
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i].x >> a[i].y;
        }
        dp(2, 1);
        double ans = dist(2, 1) + d[2][1];
        printf("%.2f\n", ans);
    }
    return 0;
}

其实可以不用dist数组来存i,j之间的距离,反而浪费内存空间,而且时间好像并没有缩短。。
好吧,我真菜。。。

附上紫薯讲解

似乎“从左到右再回来”不太好想,可以改成“两个人同时从最左端出发,沿着两条不同的路径走,最后走到最右端,除了起点和终点的其他每个点都恰好被一个人经过”。

所以
我们用d(i,j)表示1——max(i,j)全部走过,且两个人的当前位置分别是i和j,还需走的距离。那么就有d(i,j)= d(j,i),因此就从现在开始规定在状态里i>j。这样,不管是那个人,下一步只能走到 i+1,i+2……这些点。但如果走到i+2,情况就会变成“1~i和i+2,但是i+1没走过”,无法表示成状态。

所以,我们禁止这样的决策!!!!
也就是说,状态只能转移到d(i+1,j)和d(i+1,i)。不过这样就会导致一个问题:漏解。但这道题并不会,因为如果第一个人直接走到了i+2,那么就不会走到i+1了,所以只能靠第二个人走到i+1。既然如此,就不会丢失解。

另外
边界是d(n-1,j)= dist(n-1,n)+dist(j,n),其中dist(a,b)表示点a和b之间的距离。状态数有n2个,每个状态的决策有2个,时间复杂度是O(n2).

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值