该题是《算法竞赛入门经典(第二版)》的一道例题,难度不算大。我先在没看题解的情况下自己做了一遍,虽然最终通过了,思路与书上的也一样。但比书上的代码复杂了很多,可见自己对问题的处理还是有所欠缺。

  该题类似于数字三角形问题,处理的方式就是从倒数第二列逐步推到第一列, 每次选择其后一列权值最小的那条路径。最终找到花费最小的那个即可。由于出现多个答案时要输出字典序考前的路径,所以在选择路径的时候如果出现相同,要选择行数小的那个。我在处理这个问题时用了很多条件语句,使得最终的代码很长。下面是我的代码:

  

  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型数组标记每个点是否可到达,首先标记终点可到达,然后从倒数第二列循环,选择后一列中可以到达且权值最小,行号最小的一条路径,并标记该点可到达,如果后面没有可到达的点,就标记该点不可到达。循环到第一列时,只需要考虑起点即可。其他类似于原题。