AcWing 275. 傳紙條
題目
給一個 m 行 n 列矩陣,當前位置在(1,1),需要走到(m,n)然後再走回來,走到一個點可以拿走當前的值,但是每個點只能走一次,求最後能得到的最大值。
分析
如果只有一條路線就很簡單了。現在要回去,可以看成同時從(1,1)走兩條不相交的路線,狀態表示可以用 從 (1,1) 分別走到兩個點。
進一步優化狀態,可以記錄走了 k 步,和兩點的 x 座標。
①: 狀態表示(經驗)
- 集合: 表示所有兩條路線從 (1,1) 分別走到 的所有方案(不相交)
- 屬性:表示集合中所有方案路徑花費之和的最大值。
②: 狀態轉移
兩條路線分別可以從右、下走到當前位置 ,排列組合一下就是 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;
}