该题是《算法竞赛入门经典(第二版)》的一道例题,难度不算大。我先在没看题解的情况下自己做了一遍,虽然最终通过了,思路与书上的也一样。但比书上的代码复杂了很多,可见自己对问题的处理还是有所欠缺。
该题类似于数字三角形问题,处理的方式就是从倒数第二列逐步推到第一列, 每次选择其后一列权值最小的那条路径。最终找到花费最小的那个即可。由于出现多个答案时要输出字典序考前的路径,所以在选择路径的时候如果出现相同,要选择行数小的那个。我在处理这个问题时用了很多条件语句,使得最终的代码很长。下面是我的代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 using namespace std; 6 7 long long a[12][110]; 8 int pre[12][110]; 9 10 int main() 11 { 12 int m, n, i, j, temw, temr; 13 14 while(scanf("%d%d", &m, &n) == 2) 15 { 16 for(i = 1; i <= m; i++) 17 { 18 for(j = 1; j <= n; j++) 19 { 20 scanf("%lld", &a[i][j]); 21 } 22 } 23 24 for(j = n-1; j >= 1; j--) 25 { 26 for(i = 1; i <= m; i++) 27 { 28 //首先考虑向上方向的路径 29 if(i == 1) //第一行,向上方向走到最后一行 30 { 31 temw = a[m][j + 1]; 32 temr = m; 33 } 34 else 35 { 36 temw = a[i - 1][j + 1]; 37 temr = i - 1; 38 } 39 //考虑向正前方向的路径 40 if(a[i][j + 1] < temw) 41 { 42 temw = a[i][j + 1]; 43 temr = i; 44 } 45 else if(a[i][j + 1] == temw) 46 { 47 temr = min(temr, i); //相等取行号小的。 48 } 49 //考虑向下方向的路径 50 if(i == m) //最后一行,向下走到第一行 51 { 52 if(a[1][j + 1] < temw) 53 { 54 temw = a[1][j + 1]; 55 temr = 1; 56 } 57 else if(a[1][j + 1] == temw) 58 { 59 temr = min(temr, 1); 60 } 61 } 62 else 63 { 64 if(a[i + 1][j + 1] < temw) 65 { 66 temw = a[i + 1][j + 1]; 67 temr = i + 1; 68 } 69 else if(a[i + 1][j + 1] == temw) 70 { 71 temr = min(temr, i + 1); 72 } 73 } 74 a[i][j] += temw; 75 pre[i][j] = temr; //记录路径 76 } 77 } 78 temw = a[1][1]; 79 temr = 1; 80 for(i = 2; i <= m; i++) //寻找最小的。 81 { 82 if(a[i][1] < temw) 83 { 84 temw = a[i][1]; 85 temr = i; 86 } 87 } 88 //输出路径 89 if(n == 1) 90 { 91 printf("%d\n", temr); 92 } 93 else 94 { 95 for(i = temr, j = 1; j <= n-1;) 96 { 97 printf("%d ", i); 98 i = pre[i][j]; 99 j++; 100 } 101 printf("%d\n", i); 102 } 103 printf("%lld\n", a[temr][1]); 104 } 105 return 0; 106 }
显然,在处理三个方向时,用了很多代码,而书上是这样做的:
1 int rows[3] = {i, i-1, i+1}; //普通情况下的行号 2 if(i == 0) //书上第一行行号为0,如果是第一行,向上的行号需要改变。 3 rows[1] = m - 1; //最后一列列号为m-1 4 if(i == m-1) 5 rows[2] = 0; 6 d[i][j] = INF; //数组d用来存储权值 7 sort(rows, rows+3); //先排序,再比较 8 for(int k = 0; k < 3; k++) 9 { 10 int v = d[rows[k]][j+1] + a[i][j]; 11 if(v < d[i][j]) 12 { 13 d[i][j] = v; 14 next[i][j] = rows[k]; 15 } 16 }
显然,书上通过先排序再比较,这样从最小的行号开始,找最小权值,找到的最小权值一定是行号最小的,减小了很多代码量。
还需要说的一点是,这道题我一开始看错了题意!!!这是比赛的大忌! 根据所给的图,想当然地以为是从第一行第一列开始走到最后一行最后一列。顺便说一下这种情况的我的思路吧。这种情况下,需要一个bool型数组标记每个点是否可到达,首先标记终点可到达,然后从倒数第二列循环,选择后一列中可以到达且权值最小,行号最小的一条路径,并标记该点可到达,如果后面没有可到达的点,就标记该点不可到达。循环到第一列时,只需要考虑起点即可。其他类似于原题。