Description
HB要办个签证,办证处是一座 M 层的大楼,每层楼都有 N 个办公室,编号为1..N,每个办公室有一个签证员,签证需要让第 M 层的某个签证员盖章才有效。每个签证员都要满足下面三个条件之一才会给HB盖章:
- 这个签证员在1楼。
- HB的签证已经给这个签证员的正楼下(房间号相同)的签证员盖过章了。
- HB的签证已经给这个签证员的相邻房间(房间号相差1,楼层相同)的签证员盖过章了。
每个签证员盖章都要收取一定费用,这个费用不超过1000000000。找出费用最小的盖章路线,使签证生效。
Input
第1行两个整数 M 和 N 。 接下来M行每行 N 个整数,第i行第j个数表示第i层的第j个签证员收取的费用。
Output
输出最小的费用。
Sample Input 1
3 4
10 10 1 10
2 2 2 10
1 10 10 10
Sample Output 1
8
Hint
1<=M<=100,1<=N<=500
乍一看很简单,只需要设状态函数f(i,j)表示到第i层第j个房间需要的最小花费,转移方程:
(f(i,j)=min(f(i-1,j),f(i,j-1),f(i,j+1))+a[i][j])
其中a[i][j]为当前房间的花费。
初始化
(f(i,j)=inf)
(f(1,j)=a[1][j]|1<=j<=m)
但是如果每一层这样去转移,无论是从左向右计算还是从右向左计算都有后效性,因为如果从左向右更新,那么计算f(i,j)时用到的f(i,j+1)还没有更新过,会引起错误
解决方法是每一层先从左向右走用f(i,j-1)更新f(i,j),走完这层再从右向左走用f(i,j+1)更新f(i,j),保证了计算时用到的数都不是未知的。
(Ans=min(f(n,j)|1<=j<=m))
我的代码里是从上向下走的,区别不大
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<iostream>
#include<list>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define inf 0x3f3f3f3f
using namespace std;
int n,m;
int a[105][505];
int f[105][505];
void init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
for(int i=0;i<=n+1;i++)
for(int j=0;j<=m+1;j++)
f[i][j]=inf;
}
void dp(){
for(int i=1;i<=m;i++)f[n][i]=a[n][i];
for(int i=n-1;i>=1;i--){
for(int j=m;j>=1;j--)
f[i][j]=min(f[i+1][j],f[i][j+1])+a[i][j];//合并右走和下走
for(int j=1;j<=m;j++)//分开循环防后效
f[i][j]=min(f[i][j],f[i][j-1]+a[i][j]);//左走
}
int ans=inf;
for(int i=1;i<=m;i++)
ans=min(ans,f[1][i]);
printf("%d",ans);
}
int main(){
init();
dp();
return 0;
}