思路
这道题其实是一道特殊的DAG类动态规划;题目要求只能向右走,且只能向右上,右,右下三个方向,同时上部和下部是联通的;
1.动态规划求最小值
首先想到用DP[i][j]表示到达每一个点要用的最小的结果,动态转移方程为:
DP[i][j] = min{DP[i+1][j+1],DP[i-1][j+1],DP[i][j+1]} + Map[i][j];
其中,i表示行数,j表示列数。
由状态转移方程可知,在遍历j的时候需要逆推。
2.记录最优解路径
紫书上使用的方法是使用一个Next数组记录;Next[i][j]:表示在坐标为(i,j)的地方的下一个的行数。
在计算的时候动态更新,同时用一个First变量记录起点行数。
3.最小字典序
题目要求,当存在多个最优解的时候,输出最小字典序的最优解。
由于每一个阶段的时候有三个选择,可以将这三个选择(行数)记录,进行排序,从而保证解的最小字典序;
4.避开大坑
Uva 116的数据里有列数为1的数据,需要单独处理。
以下为AC代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 100000;
int M,N;
int Map[10+5][100+1] = {0};
int Next[10+5][100+1] = {0};
int DP[10+5][100+15] = {0};
//DP[i][j] = min{DP[i+1][j+1],DP[i-1][j+1],DP[i][j+1]} + Map[i][j];
//Next[i][j]:表示在坐标为(i,j)的地方的下一个的行数;
int main(void)
{
while(scanf("%d%d",&M,&N) != EOF)
{
for(int i = 1;i <= M;i++)
{
for(int j = 1;j <= N;j++)
{
cin >> Map[i][j];
}
}
int First = 1;
int ans = INF;
//DP过程
for(int i = 1; i <= M; i++)
{
DP[i][N] = Map[i][N];
}
//注意数据中存在列数为1的超坑数据
if(N == 1)
{
for(int i = 1; i <= M; i++)
{
if(DP[i][1]<ans)
{
ans = DP[i][1];
First = i;
}
}
}
else //注意这里的顺序,顺序可以由DP方程推知
{
for(int j = N-1;j > 0;j--)
{
for(int i = 1;i <= M;i++)
{
DP[i][j] = INF;
int Rows[3] = {i-1,i,i+1};
if(i == 1)
{
Rows[0] = M;
}
if(i == M)
{
Rows[2] = 1;
}
sort(Rows,Rows+3);
for(int k = 0;k < 3;k++)
{
int Val = DP[Rows[k]][j+1] + Map[i][j];
if(Val < DP[i][j])
{
DP[i][j] = Val;
Next[i][j] = Rows[k];
}
}
if(j == 1 && DP[i][j] < ans)
{
ans = DP[i][j];
First = i;
}
}
}
}
int r,j;
printf("%d",First);
for(int j=1,r=First;j<N;j++)
{
r=Next[r][j];
printf(" %d",r);
}
printf("\n%d\n",ans);
}
return 0;
}
学习知识点:动态规划路径记录与输出