UVa 116 - Unidirectional TSP(DP)

31 篇文章 0 订阅

题目大意是给一个矩阵,要求从矩阵的最左边走到最右边,使得走过的路径上所有数字的加和最小。

方法就是从矩阵的一头找到另一头,使每一列上面的加和都最小。使用了一个数组dp,dp(i,j)保存的是到达(i,j)这个点的时候最小的值。然后就是考虑是从矩阵的左边向右边寻找答案还是从矩阵的右边向左边寻找答案。最开始想的是从左向右,但是代码写出来以后发现一直wa,想了半天感觉到可能是字典序出问题了。于是只能从右向左找答案。具体原因下面会分析。

动态规划方程是dp(i,j)=min{dp(t,j+1)+matrix(i,j),dp(i,j)}其中t是可以到达i这个点的所有可能的点。因为题目中说过到达(i,j)这个点可以是三个点。这个方程的意思就是从所有可以到达(i,j)的点中找到一个使权值最小的。

在规划的过程中,要将所得的路径保存下来。方法就是使用一个数组。

代码如下:

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<climits>
using namespace std;
int m,n;

const int INF=INT_MAX;
int matrix[15][105];
int dp[15][105];
int next[15][105];
int ans[105];

int getnext(int i,int t){
	t=t-2;
	i+=t;
	if(i<0)i=m-1;
	else if(i==m)i=0;
	return i;
}

int main(){
//	freopen("data.txt","r",stdin);
	while(scanf("%d%d",&m,&n)!=EOF){
		for(int i=0;i<m;++i){
			for(int t=0;t<n;++t){
				scanf("%d",&matrix[i][t]);
			}
		}
		memset(dp,0,sizeof(dp));
		for(int i=0;i<m;++i){
			dp[i][n-1]=matrix[i][n-1];
			next[i][n-1]=i;
		}
		for(int start=n-2;start>=0;--start){
			for(int i=0;i<m;++i){
				dp[i][start]=INF;
				next[i][start]=200;
				int s;
				if(m==2)s=2;
				else s=1;
				for(int t=s;t<=3;++t){
					int pos=getnext(i,t);
					if(dp[i][start]>dp[pos][start+1]+matrix[i][start]){
						dp[i][start]=dp[pos][start+1]+matrix[i][start];
						next[i][start]=pos;
					}
					else if(dp[i][start]==dp[pos][start+1]+matrix[i][start]&&pos<next[i][start]){
						dp[i][start]=dp[pos][start+1]+matrix[i][start];
						next[i][start]=pos;
					}
				}//for t
//				cout<<i<<' '<<start<<' '<<dp[i][start]<<' '<<matrix[i][start]<<endl;
			}//for i
		}//for start
		int minn=INF;
		int ansi;
		for(int i=0;i<m;++i){
			if(dp[i][0]<minn){
				minn=dp[i][0];
				ansi=i;
			}
		}
//		cout<<"minn="<<minn<<endl;
		ans[0]=ansi;
		int pos=ansi;
		for(int i=0;i<n;++i){
			ans[i+1]=next[pos][i];
			pos=next[pos][i];
		}
		cout<<ans[0]+1;
		for(int i=1;i<n;++i){
			cout<<' '<<ans[i]+1;
		}
		cout<<endl;
		cout<<dp[ansi][0]<<endl;
	}//while
	return 0;
}
下面来分析一下为什么只能从后往前不能从前往后。

首先对于字典序的比较我们是从最高位比较向最低位。对于这里,路径的比较就是从左比较向右。next数组相当于是一个指针,指向每个节点的后继。在循环

for(int i=0;i<m;++i){
	if(dp[i][0]<minn){
		minn=dp[i][0];
		ansi=i;
	}
}
中我们对所得路径的最高位进行了筛选。得到的是权值最小而且是最高位最小的数。在先前对next数组进行赋值的时候我们完成了对后续每一位的比较。因为每次我们建立指针的时候指向的都是后继中权值最小且位置最小的元素。

如果我们从前向后找,那么这个时候next数组指向的就是每个元素的前驱。在这种情况下,给next数组赋值的时候相当于是找到使得当前元素的前驱的位置最小的元素,与字典序比较的顺序是相反的(字典序的比较是从前往后,这里建立的next数组相当于是从后往前了)。当next数组赋值完成的时候,相当于是找到了将路径反过来看的最小的字典序。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值