方格取数
方格取数
设有 $N \times N$ 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字 $0$。如下图所示:
某人从图中的左上角 $A$ 出发,可以向下行走,也可以向右行走,直到到达右下角的 $B$ 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 $0$)。
此人从 $A$ 点到 $B$ 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
输入格式
第一行为一个整数 $N$,表示 $N \times N$ 的方格图。
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
行和列编号从 $1$ 开始。
一行“$0~0~0$”表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
数据范围
$N \leq 10$
输入样例:
8 2 3 13 2 6 6 3 5 7 4 4 14 5 2 21 5 6 4 6 3 15 7 2 14 0 0 0
输出样例:
67
解题思路
一开始想到一个贪心的思路,就是两遍dp。第一遍dp得到从$\left( {1, 1} \right)$到$\left( {n, n} \right)$的最大值,然后把走过的格子的值置成$0$,然后继续进行一次dp,得到从$\left( {1, 1} \right)$到$\left( {n, n} \right)$的最大值,两次得到的最大值的和就是答案。但这种贪心做法是错的,因为第一次dp的结果会影响第二次dp的结果,举个反例,一开始格子的值如下:
因此这题应该用dp。由于是两条路径,因此状态应该有$4$维,即$f \left( {i,j,u,v} \right)$表示从$\left( {1, 1} \right)$走到$\left( {i, j} \right)$的第$1$条路径以及从$\left( {1, 1} \right)$走到$\left( {u, v} \right)$的第$2$条路径的集合。根据分别到达$\left( {i, j} \right)$和$\left( {u, v} \right)$的方向进行状态划分,共$2^2$种状态,状态转移方程就是$$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$$
其中$$w= \begin{cases} w_{ij}+w_{uv} &\left( {i \ne u ~|~ j \ne v} \right) \\ w_{ij} &\left( {i = u ~\&~ j = v} \right) \end{cases}$$
如果有$i = u$并且$j = v$,那么应该算一个格子值,即状态转移方程应该只加上$w_{ij}$而不再加同一个格子的值$w_{uv}$。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 20; 5 6 int g[N][N], f[N][N][N][N]; 7 8 int main() { 9 int n, a, b, c; 10 scanf("%d", &n); 11 while (scanf("%d %d %d", &a, &b, &c), a || b || c) { 12 g[a][b] = c; 13 } 14 15 for (int i = 1; i <= n; i++) { 16 for (int j = 1; j <= n; j++) { 17 for (int u = 1; u <= n; u++) { 18 for (int v = 1; v <= n; v++) { 19 int w = g[i][j]; 20 if (i != u || j != v) w += g[u][v]; // 不是同一个格子 21 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]}) + w; 22 } 23 } 24 } 25 } 26 27 printf("%d", f[n][n][n][n]); 28 29 return 0; 30 }
可以发现,当$i+j = u+v$的时候,两条路径的格子才可能重合。因此我们可以规定两条路径是同时出发的,这样就可以优化掉一维,得到$f \left( {k,i,j} \right)$。其中$k$为两条路径的横纵坐标的和(因为两条路径是同时出发的,因此两条路径的横纵坐标和相同),$i$为第一条路径的横坐标,$j$为第二条路径的横坐标。$k-i$就得到第一条路径的纵坐标,$k-j$就得到第二条路径的纵坐标。
如果$i \ne j$,说明不是在同一个格子。状态转移方程为$$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$$
其中$$w= \begin{cases} w_{ij}+w_{ij} &\left( {i \ne j} \right) \\ w_{ij} &\left( {i = j} \right) \end{cases}$$
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 30; 5 6 int g[N][N], f[N << 1][N][N]; 7 8 int main() { 9 int n, a, b, c; 10 scanf("%d", &n); 11 while (scanf("%d %d %d", &a, &b, &c), a || b || c) { 12 g[a][b] = c; 13 } 14 15 for (int k = 2; k <= n + n; k++) { 16 for (int i = 1; i <= n; i++) { 17 for (int j = 1; j <= n; j++) { 18 if (k - i < 0 || k - j < 0) continue; // 判断纵坐标是否越界 19 int w = g[i][k - i]; 20 if (i != j) w += g[j][k - j]; // 两个格子不同 21 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; 22 } 23 } 24 } 25 26 printf("%d", f[n + n][n][n]); 27 28 return 0; 29 }
参考资料
AcWing 1027. 方格取数:https://www.acwing.com/video/354/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16492481.html