cqyz oj | 【训练题】HB办证 P1419 | DP动态规划

Description

HB要办个签证,办证处是一座 M 层的大楼,每层楼都有 N 个办公室,编号为1..N,每个办公室有一个签证员,签证需要让第 M 层的某个签证员盖章才有效。每个签证员都要满足下面三个条件之一才会给HB盖章:

  1. 这个签证员在1楼。
  2. HB的签证已经给这个签证员的正楼下(房间号相同)的签证员盖过章了。
  3. 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;
}

猜你喜欢

转载自www.cnblogs.com/de-compass/p/11234809.html
今日推荐