<—点个赞吧QwQ
宣传一下算法提高课整理
一次素质拓展活动中,班上同学安排坐成一个 $ m $ 行 $ n $ 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。
幸运的是,他们可以通过传纸条来进行交流。
纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 $ (1,1) $ ,小轩坐在矩阵的右下角,坐标 $ (m,n) $ 。
从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。
班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 $ 0 $ 表示),可以用一个 $ 0 \sim 100 $ 的自然数来表示,数越大表示越好心。
小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。
现在,请你帮助小渊和小轩找到这样的两条路径。
输入格式
第一行有 $ 2 $ 个用空格隔开的整数 $ m $ 和 $ n $ ,表示学生矩阵有 $ m $ 行 $ n $ 列。
接下来的 $ m $ 行是一个 $ m \times n $ 的矩阵,矩阵中第 $ i $ 行 $ j $ 列的整数表示坐在第 $ i $ 行 $ j $ 列的学生的好心程度,每行的 $ n $ 个整数之间用空格隔开。
输出格式
输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。
数据范围
$ 1 \le n,m \le 50 $
输入样例:
3 3
0 3 9
2 8 5
5 7 0
输出样例:
34
思路1
注意一下思路中,重复的点权值只算一遍。
证明为何可以用方格取数的代码:
转载自这里:
首先, 从右下角回传可以等价为从左上角同时传两次。要想两个路径除了起点和终点之外没有交点,那么肯定有一条路径完全位于另一条的上方。
现在考虑路径有交点的情况:
这种情况其实转换起来很简单,只要把位于红色线段上方的蓝色线段交换颜色就可以了,也就是说当红色处于蓝色的下方的时候,将红色的路径换成从蓝色的那段走是等效的(因为两条路径加起来经过的节点完全没有变)。
就可以得到:
但是这个时候虽然满足了红色路径完全在蓝色的上方,但是却有交点。但是因为所有节点的权值都为非负数,那么可以证明这种情况永远不可能是最优解。比如以交点(2,2)为例,蓝色从(3,1)绕道或者红色从(1,3)处绕道一定不会比两条路径都从(2,2)处走差。
绕过交点之后,可以得到蓝色虚线的方案,该方案一定不会比之前的两个实线的方案更差。(当然该方案也不一定是最优的,还要确定应该由蓝色还是红色来走原来的交点的位置。
我们假设有两个人同时走,每次走一步,而他们的走过的距离总是一样的。
闫氏 $ \text{DP} $ 分析法:
状态表示:$ f_{i_1,j_1,i_2,j_2} $
- 集合:$ f_{i_1,j_1,i_2,j_2} $ 表示第一个人从 $ (1,1) $ 走到 $ (i_1,j_1) $ ,而第二个人从 $ 1,1 $ 走到 $ (i_2,j_2) $
- 属性:$ \max $
状态计算:
- 第一个点可以从上面和左边走过来
- 第二个点也可以从上面和左边走过来
- 所以根据乘法原理,一个有 $ 4 $ 种方案,我们得更新 $ f_{i_1,j_1,i_2,j_2} $
- 所以状态转移方程是 $ f_{i_1,j_1,i_2,j_2}=\max \lbrace f_{i_1-1,j_1,i_2-1,j_2},f_{i_1-1,j_1,i_2,j_2-1},f_{i_1,j_1-1,i_2-1,j_2},f_{i_1,j_1-1,i_2,j_2-1}\rbrace+t $
(其中 $ t $ 是价值和,当 $ (i_1,j_1),(i_2,j_2) $ 是同一个点时,$ t=a_{i_1,j_1} $ 否则 $ t=a_{i_1,j_1}+a_{i_2,j_2} $
答案:
- 根据定义,答案就是 $ f_{n,n,n,n} $
代码1
#include <iostream>
using namespace std;
const int N = 60;
int n,m;
int a[N][N];
int f[N][N][N][N];
int main () {
cin >> n >> m;
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= m;j++) cin >> a[i][j];
}
for (int i1 = 1;i1 <= n;i1++) {
for (int j1 = 1;j1 <= m;j1++) {
for (int i2 = 1;i2 <= n;i2++) {
for (int j2 = 1;j2 <= m;j2++) {
if (i1 + j1 != i2 + j2) continue;
int &ans = f[i1][j1][i2][j2];
ans = max (ans,f[i1 - 1][j1][i2 - 1][j2]);
ans = max (ans,f[i1 - 1][j1][i2][j2 - 1]);
ans = max (ans,f[i1][j1 - 1][i2 - 1][j2]);
ans = max (ans,f[i1][j1 - 1][i2][j2 - 1]);
if (i1 == i2 && j1 == j2) ans += a[i1][j1];
else ans += a[i1][j1] + a[i2][j2];
}
}
}
}
cout << f[n][m][n][m] << endl;
return 0;
}
思路2
因为两个人走的路长是一样的,我们可以用一位 $ k $ 表示他们的和,剩下的两维就是两个横坐标。
闫氏 $ \text{DP} $ 分析法:
状态表示:$ f_{k,i_1,i_2} $
- 集合:$ f_{k,i_1,i_2} $ 表示第一个人从 $ (1,1) $ 走到 $ (i_1,k - i_1) $ ,而第二个人从 $ 1,1 $ 走到 $ (i_2,k - i_2) $ (当存在的时候)
- 属性:$ Max $
状态计算:
- 第一个点可以从上面和左边走过来
- 第二个点也可以从上面和左边走过来
- 所以根据乘法原理,一个有 $ 4 $ 种方案,我们得一一更新 $ f_{k,i_1,i_2} $
- 所以状态转移方程是 $ f_{k,i_1,i_2}=\max \lbrace f_{k-1,i_1-1,i_2-1},f_{k-1,i_1-1,i_2},f_{k-1,i_1,i_2-1},f_{k-1,i_1,i_2}\rbrace+t $
(其中 $ t $ 是价值和,当 $ (i_1,k-i_1) $ 和 $ (i_2,k-i_2) $ 是同一个点时,$ t=a_{i_1,k-i_1} $ 否则 $ t=a_{i_1,k-i_1}+a_{i_2,k-i_2} $
答案:
- 根据定义,答案就是 $ f_{2\times n,n,n} $
代码2
#include <iostream>
using namespace std;
const int N = 60;
int n,m;
int a[N][N];
int f[2 * N][N][N];
int main () {
cin >> n >> m;
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= m;j++) cin >> a[i][j];
}
for (int k = 2;k <= n + m;k++) {
for (int i1 = 1;i1 <= n;i1++) {
for (int i2 = 1;i2 <= n;i2++) {
int j1 = k - i1,j2 = k - i2;
if (j1 < 1 || j1 > m || j2 < 1 || j2 > m) continue;
int &ans = f[k][i1][i2];
int w = a[i1][j1];
if (i1 != i2 || j1 != j2) w += a[i2][j2];
ans = max (ans,f[k - 1][i1 - 1][i2 - 1] + w);
ans = max (ans,f[k - 1][i1 - 1][i2] + w);
ans = max (ans,f[k - 1][i1][i2 - 1] + w);
ans = max (ans,f[k - 1][i1][i2] + w);
}
}
}
cout << f[n + m][n][n] << endl;
return 0;
}
Orz
%%%
题目描述貌似是摘花生的
搞错了(尴尬
nb
佬,这两代码都不行呀,都超时了
我修一下
done