给定一个m行n列(m<=10,n<=100)的整数矩阵,从第一列任何一个位置出发每次往右、右上或者右下走一格,最终到达最后一列。要求经过的整数之和最小。整个矩阵是环形的,即第一行的上一行是最后一行,最后一行的下一行是第一行。输出路径上每列的行号。多解时输出字典序最小的。图9-5中是两个矩阵和对应的最优路径(唯一的区别是最后一行)。
图9-5
Input sample
5 6
3 4 1 2 8 6
6 1 8 2 7 4
5 9 3 9 9 5
8 4 1 3 2 6
3 7 2 8 6 4
5 6
3 4 1 2 8 6
6 1 8 2 7 4
5 9 3 9 9 5
8 4 1 3 2 6
3 7 2 1 2 3
2 2
9 10 9 10
Output sample
1 2 3 4 4 5
16
1 2 1 5 4 5
11
1 1
19
————————————————分割の线——————————————
分析
总算是一道偏水的题目了……
已知到点(i,j)的值的大小与后续的路径没有关系,所以可以定义f[i][j]表示到点(i,j)的最小值;
转移方程如下
f[i][j]=min(f[i+1][j-1],f[i+1][j],f[i+1][j+1])+a[i][j]
小细节:由于是环形的矩阵,所以要对(j-1)和(j+1)做一个处理。处理完后,对三个位置进行排序,在值相同时,求最小位置。
关于路径,可以运用opt[i][j]储存然后顺序输出即可。
代码如下:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int a[12][102];//保存点的数值
int f[12][102];//保存最小值(最优状态)
int vis[12][102];//标记是否做过该点
int opt[12][102];//保存操作
int n,m;
int change(int x)//完成矩阵到环形的转换
{
while(x<1) x+=n;
while(x>n)x-=n;
return x;
}
void print(int x,int y)//递归输出路径,貌似用while也可以
{
if(y==m) printf("%d",x);
else
{
printf("%d ",x);
print(opt[x][y],y+1);
}
return ;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
memset(f,-1,sizeof(f));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
f[i][m]=a[i][m],vis[i][m]=1;
for(int i=m-1;i>=1;i--)//逆推,方便求最小字典序的路径
for(int j=1;j<=n;j++)
{
int b[3];
b[0]=change(j-1);
b[1]=change(j);
b[2]=change(j+1);
sort(b,b+3);//排序以保证到该店的路径最小
if(vis[j][i]==0||f[j][i]>f[b[0]][i+1]+a[j][i])
{
vis[j][i]=1;
f[j][i]=f[b[0]][i+1]+a[j][i];
opt[j][i]=b[0];
}
if(vis[j][i]==0||f[j][i]>f[b[1]][i+1]+a[j][i])
{
vis[j][i]=1;
f[j][i]=f[b[1]][i+1]+a[j][i];
opt[j][i]=b[1];
}
if(vis[j][i]==0||f[j][i]>f[b[2]][i+1]+a[j][i])
{
vis[j][i]=1;
f[j][i]=f[b[2]][i+1]+a[j][i];
opt[j][i]=b[2];
}
}
int ans=0x7fffffff,pos=0;
for(int i=1;i<=n;i++)
{
if(f[i][1]<ans)
{
ans=f[i][1];
pos=i;//记录路径起点和最小值
}
}
print(pos,1);//输出路径
cout<<endl<<ans<<endl;//输出最小值
}
return 0;
}