题目描述:
一个商人穿过一个N×N的正方形的网格,去参加一个非常重要的商务活动。
他要从网格的左上角进,右下角出。
每穿越中间1个小方格,都要花费1个单位时间。
商人必须在(2N-1)个单位时间穿越出去。
而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。
请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。
输入格式
第一行是一个整数,表示正方形的宽度N。
后面N行,每行N个不大于100的整数,为网格上每个小方格的费用。
输出格式
输出一个整数,表示至少需要的费用。
数据范围
1≤N≤100
输入样例:
5
1 4 6 8 10
2 5 7 15 17
6 8 9 18 20
10 11 12 19 21
20 23 25 29 33
输出样例:
109
样例解释
样例中,最小值为109=1+2+5+7+9+12+19+21+33。
分析:
本题与上一题一样,同样是从左上角进入,走到右下角。2N - 1的时间限制保证了商人不能走回头路,亦即只能向下或者向右走,摘花生问题要求的是摘得的最大花生数量,而本题所求的是最小的费用。一个求最大值一个求最小值,虽然只是一字之差,比较起来却完全不同了。
状态表示:f[i][j]表示从(1,1)走到(i,j)的最小费用,状态转移方程:f[i][j] = min(f[i-1][j],f[i][j-1]) + w[i][j]。由于f数组默认初值是0,所以求第一行状态时只能从左边转移过来而不能从上边转移过来,否则求最小值会发生错误;同样求第一列状态时只能从上边的状态转移过来而不能从左边的状态转移过来。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 105;
int n,a[N][N],f[N][N];
int main()
{cin >> n;for(int i = 1 ; i <= n ; i++)for(int j = 1 , x; j <= n ; j++){cin >> x;if(i == 1)f[i][j] = f[i][j-1] + x;else if(j == 1)f[i][j] = f[i-1][j] + x;else f[i][j] = min(f[i-1][j] , f[i][j-1]) + x;}cout << f[n][n] << endl;
}
采用滚动数组实现便没有那么麻烦了,既然f的初值是0会影响到最小值的比较,我们把f的初值设置为INF即可。那么问题来了,求第一行第一列状态f[1]时,f[1] = min(f[0],f[1]) + w[1],如果f数组全部是INF,那么f[1]就将计算错误。解决这个问题的办法是将f数组除了f[1]以外都设为INF,这样在求f[1]时,由于f[1]的初值是0就不会出错了,求完f[1]后f[1]的值便不是0了,也不会影响下一行状态的计算。可能有人会想为什么不把f[0]设为0,这样f[1]不也是可以正确求解嘛,实际上,f[0]是所有第一列状态的哨兵节点,每次计算f[1]时均需要与f[0]比较,所有f[0]设为INF较好。这样一个简单的初始化设置,很好的解决了最小值比较的边界问题,使得用滚动数组实现本题无须再进行边界特判了。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=105;
int w[N],f[N];
int main(){int n;scanf("%d", &n);memset(f,0x3f,sizeof f);f[1] = 0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d", &w[j]),f[j]=min(f[j],f[j-1])+w[j];printf("%d\n", f[n]);return 0;
}