​AcWing 275. 传纸条​

题目

给一个 m 行 n 列矩阵,当前位置在(1,1),需要走到(m,n)然后再走回来,走到一个点可以拿走当前的值,但是每个点只能走一次,求最后能得到的最大值。

分析

如果只有一条路线就很简单了。现在要回去,可以看成同时从(1,1)走两条不相交的路线,状态表示可以用 AcWing 275. 传纸条 (dp 状态优化)_#include

进一步优化状态,可以记录走了 k 步,和两点的 x 坐标。

①: 状态表示(经验)

  1. 集合:AcWing 275. 传纸条 (dp 状态优化)_i++_02 表示所有两条路线从 (1,1) 分别走到 AcWing 275. 传纸条 (dp 状态优化)_#include_03的所有方案(不相交)
  2. 属性:表示集合中所有方案路径花费之和的最大值

②: 状态转移

两条路线分别可以从右、下走到当前位置 AcWing 275. 传纸条 (dp 状态优化)_#include_04,排列组合一下就是 4 种方式。

推广:如果这个题要走 ​​k 条路径​​的话,就不能用 dp 来做了,状态太多,要用最小费用最大流

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const int N = 50 + 5;

int n, m, t;
int w[N][N];
int dp[N * 2][N][N];

int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &w[i][j]);

for (int k = 2; k <= n + m; k++)
for (int x1 = max(1, k - m); x1 <= min(k - 1, n); x1++)
for (int x2 = max(1, k - m); x2 <= min(k - 1, n); x2++) {
if (x1 == x2) continue; // 不能走相同的点
if (x1 != x2) t = w[x1][k - x1] + w[x2][k - x2];
for (int a = 0; a < 2; a++) // 四个方向
for (int b = 0; b < 2; b++)
dp[k][x1][x2] = max(dp[k][x1][x2], dp[k - 1][x1 - a][x2 - b] + t);
}
// 最后一个点的值 加上 两个方向来的最大值
printf("%d\n", w[n][m] + max(dp[n + m - 1][n][n - 1], dp[n + m - 1][n - 1][n]));
return 0;
}

代码其实也可以写成,其中如果两人在相同格子,则 t 等于这个格子的分值;否则等于两个格子的分值之和。主要是为了计算最后一个点,中间走相同点的肯定会被其他值替换,所以没事。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const int N = 50 + 5;

int n, m;
int w[N][N];
int dp[N * 2][N][N];

int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &w[i][j]);

for (int k = 2; k <= n + m; k++)
for (int x1 = max(1, k - m); x1 <= min(k - 1, n); x1++)
for (int x2 = max(1, k - m); x2 <= min(k - 1, n); x2++) {
int t = w[x1][k - x1];
if (x1 != x2) t += w[x2][k - x2];
for (int a = 0; a < 2; a++) // 四个方向
for (int b = 0; b < 2; b++)
dp[k][x1][x2] = max(dp[k][x1][x2], dp[k - 1][x1 - a][x2 - b] + t);
}
printf("%d\n", dp[n + m][n][n]);
return 0;
}