传纸条

传纸条

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。

一次素质拓展活动中,班上同学安排坐成一个 $m$ 行 $n$ 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。

幸运的是,他们可以通过传纸条来进行交流。

纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 $\left( {1,1} \right)$,小轩坐在矩阵的右下角,坐标 $\left( {m,n} \right)$。

从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。 

在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。

班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。 

还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 $0$ 表示),可以用一个 $0 \sim 100$ 的自然数来表示,数越大表示越好心。

小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。

现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式

第一行有 $2$ 个用空格隔开的整数 $m$ 和 $n$,表示学生矩阵有 $m$ 行 $n$ 列。

接下来的 $m$ 行是一个 $m \times n$ 的矩阵,矩阵中第 $i$ 行 $j$ 列的整数表示坐在第 $i$ 行 $j$ 列的学生的好心程度,每行的 $n$ 个整数之间用空格隔开。

输出格式

输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

数据范围

$1 \leq n, m \leq 50$

输入样例:

3 3
0 3 9
2 8 5
5 7 0

输出样例:

34

 

解题思路

  这题与方格取数很像,不同的地方就是两条路径种不允许有格子被重复走了两次,即两条路径不可以相交(起点与终点除外)。

  状态的定义也是$f \left( {i,j,u,v} \right)$,状态转移方程为$$f \left( {i,j,u,v} \right) = max\left\{ {f \left( {i-1,j,u-1,v} \right),~ f \left( {i-1,j,u,v-1} \right),~ f \left( {i,j-1,u-1,v} \right),~ f \left( {i,j-1,u,v-1} \right)} \right\} + w_{ij} + w_{uv}$$

  其中如果两条路径走到同一个格子,即有$i=u,~ j=v$,这种情况就应该跳过,其余的与方格取数这题一样。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 60;
 5 
 6 int g[N][N], f[N][N][N][N];
 7 
 8 int main() {
 9     int n, m;
10     scanf("%d %d", &n, &m);
11     for (int i = 1; i <= n; i++) {
12         for (int j = 1; j <= m; j++) {
13             scanf("%d", &g[i][j]);
14         }
15     }
16     
17     f[1][1][1][1] = g[1][1];
18     for (int i = 1; i <= n; i++) {
19         for (int j = 1; j <= m; j++) {
20             for (int u = 1; u <= n; u++) {
21                 for (int v = 1; v <= m; v++) {
22                     if (i == u && j == v) continue; // 两条路径不可以有相交,即不可以经过同一个格子
23                     f[i][j][u][v] = max({f[i - 1][j][u - 1][v], f[i - 1][j][u][v - 1], f[i][j - 1][u - 1][v], f[i][j - 1][u][v - 1]}) + g[i][j] + g[u][v];
24                 }
25             }
26         }
27     }
28     
29     // 最后计算得到两条路径从g[1][1]走到g[n-1][m]或g[n][m-1]
30     printf("%d", max({f[n - 1][m][n - 1][m], f[n - 1][m][n][m - 1], f[n][m - 1][n - 1][m], f[n][m - 1][n][m - 1]}) + g[n][m]);
31     
32     return 0;
33 }

  当然也可以优化到三维的$f \left( {k,i,j} \right)$,状态转移方程为$$f \left( {k,i,j} \right) = max\left\{ {f \left( {k-1,i-1,j-1} \right),~ f \left( {k-1,i-1,j} \right),~ f \left( {k-1,i,j-1} \right),~ f \left( {k-1,i,j} \right)} \right\} + w_{ij} + w_{uv}$$

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 110;
 5 
 6 int g[N][N], f[N << 1][N][N];
 7 
 8 int main() {
 9     int n, m;
10     scanf("%d %d", &n, &m);
11     for (int i = 1; i <= n; i++) {
12         for (int j = 1; j <= m; j++) {
13             scanf("%d", &g[i][j]);
14         }
15     }
16     
17     f[2][1][1] = g[1][1];
18     for (int k = 2; k <= n + m; k++) {
19         for (int i = 1; i <= n; i++) {
20             for (int j = 1; j <= n; j++) {
21                 if (k - i < 0 || k - j < 0 || i == j) continue;
22                 f[k][i][j] = max({f[k - 1][i - 1][j - 1], f[k - 1][i - 1][j], f[k - 1][i][j - 1], f[k - 1][i][j]}) + g[i][k - i] + g[j][k - j];
23             }
24         }
25     }
26     
27     printf("%d", max({f[n + m - 1][n - 1][n - 1], f[n + m - 1][n - 1][n], f[n + m - 1][n][n - 1], f[n + m - 1][n][n]}) + g[n][m]);
28     
29     return 0;
30 }

  然而这题有个很有意思的性质,虽然题目中说两条路径不可以有交点,但实际求解的过程中可以把两条路径有相交的情况也一起算进去,而不会影响最终答案的结果。证明如下:

  首先从右下角回传可以等价为从左上角同时传两次。然后假设现在两条路径是相交的情况,如图所示:

  由于两条路径不能相交,那么意味着其中一条路径必然在另外一条路径的上方。我们可以对这个路径的位置进行交换,当红色路径在蓝色路径下方时,对这两个部分的路径进行交换(交换后的结果与原来的路径是等价的,因为两条路径经过的格子加起来完全没有改变)。得到如下结果:

  对于上图这种情况仍有相交的格子,我们可以让其中一条路径(或者两条)额外走多几个格子来避免相交的情况,比如下图:

  这样原本相交的橙色的格子现在就不会被重复经过了。而且由于每个格子的值是非负的,因此经过一次这样的调整后,会至少累加一次新格子的值,即两条路径经过的格子所累加的值会增大(或不会减少)。因此经过多次这样的调整,总是可以将相交的两条路径变成不相交的情况,并且最终经过格子所累加的值一定比原来两条路径的大。

  因此得出结论:两条路径的最优情况一定会在是不经过相同格子的路径中出现。即相交的两条路径不会影响最优路径的结果(相交的两条路径所得到的结果一定不超过不相交的两条路径所得到的结果)。

  因此这题完全可以照搬格子取数这题的代码,AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 110;
 5 
 6 int g[N][N], f[N << 1][N][N];
 7 
 8 int main() {
 9     int n, m;
10     scanf("%d %d", &n, &m);
11     for (int i = 1; i <= n; i++) {
12         for (int j = 1; j <= m; j++) {
13             scanf("%d", &g[i][j]);
14         }
15     }
16     
17     for (int k = 2; k <= n + m; k++) {
18         for (int i = 1; i <= n; i++) {
19             for (int j = 1; j <= n; j++) {
20                 if (k - i < 0 || k - j < 0) continue;
21                 int w = g[i][k - i];
22                 if (i != j) w += g[j][k - j];
23                 f[k][i][j] = max({f[k - 1][i - 1][j - 1], f[k - 1][i - 1][j], f[k - 1][i][j - 1], f[k - 1][i][j]}) + w;
24             }
25         }
26     }
27     
28     printf("%d", f[n + m][n][n]);
29     
30     return 0;
31 }

 

参考资料

  AcWing 275. 证明传纸条为何可以使用方格取数的代码:https://www.acwing.com/solution/content/12389/

posted @ 2022-07-20 19:42  onlyblues  阅读(65)  评论(0编辑  收藏  举报
Web Analytics