题目 | 方格取数
AcWing 1027. 方格取数
动态规划典型例题
https://www.acwing.com/problem/content/1029/
题目
设有 $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
题解
能否分为两次取数?
一次取数的问题很好解决,该题的难点是进行了两次取数。需要注意的一点是,不可将两次取数分别处理,因为两次分别的最优操作在总体上并不一定是最优操作。如果这点无法理解,可以参考以下示例:
$$ \begin{matrix} 0 & 100 & 100 & 1 & 0\\ 0 & 1 & 100 & 100 & 0\\ 0 & 0 & 1 & 100 & 0\\ 0 & 0 & 0 & 100 & 0\\ 0 & 0 & 0 & 0 & 0 \end{matrix} $$
如果我们将其分为两次取数来思考,第一次取数时,最优策略如下,一共取到了 $600$:
$$ \begin{matrix} \framebox{0} & \framebox{100} & \framebox{100} & 1 & 0\\ 0 & 1 & \framebox{100} & \framebox{100} & 0\\ 0 & 0 & 1 & \framebox{100} & 0\\ 0 & 0 & 0 & \framebox{100} & \framebox{0}\\ 0 & 0 & 0 & 0 & \framebox{0} \end{matrix} $$
第二次取数时,最优策略如下,一共取到了 $2$:
$$ \begin{matrix} \framebox{0} & \framebox{0} & 0 & 1 & 0\\ 0 & \framebox{1} & \framebox{0} & 0 & 0\\ 0 & 0 & \framebox{1} & \framebox{0} & \framebox{0}\\ 0 & 0 & 0 & 0 & \framebox{0}\\ 0 & 0 & 0 & 0 & \framebox{0} \end{matrix} $$
但是我们通过目测都能发现更优的取法(方框代表第一次取,圆括号代表第二次取),一共能取 $603$:
$$ \begin{matrix} \framebox{(0)} & \framebox{100} & \framebox{100} & \framebox{1} & 0\\ (0) & (1) & (100) & \framebox{100} & 0\\ 0 & 0 & (1) & \framebox{100} & 0\\ 0 & 0 & (0) & \framebox{100} & \framebox{0}\\ 0 & 0 & (0) & (0) & \framebox{(0)} \end{matrix} $$
这就是为什么两次最优不等于总体最优。
朴素解法
先考虑仅一次操作的情形,$dp(i,j)=\max\left\{dp(i-1,j),dp(i,j-1)\right\}+mat(i,j)$,其中 $dp(i,j):=$ 从 $(1,1)$ 走到 $(i,j)$ 能取到的数字的和的最大值。
我们从一次操作再推广到两次,令 $dp(i,j,k,l):=$ 两次取数分别为从 $(1,1),(1,1)$ 走到 $(i,j),(k,l)$ 的情况能取到的数字的和的最大值。
此时,转移方式有四种,来源分别为:
- 第一次向下,第二次向下:$dp(i-1,j,k-1,l)$
- 第一次向右,第二次向下:$dp(i,j-1,k-1,l)$
- 第一次向下,第二次向右:$dp(i-1,j,k,l-1)$
- 第一次向右,第二次向右:$dp(i,j-1,k,l-1)$
转移后,我们需要加上本次取得数,此时就得考虑重复取数的情况:我们不能将一个数取两次
解决方法很简单,如果两次取的数相同,则只加一次,若不同,则加上取得两个不同的数:
- 若 $i=k$ 且 $j=l$:$mat(i,j)$
- 反之:$mat(i,j)+mat(k,l)$
综上,转移方式即为:
$$ \begin{align} dp(i,j,k,l)=&max\{\\ &dp(i,j,k,l),\\ &dp(i-1,j,k-1,l)+t,\\ &dp(i,j-1,k-1,l)+t,\\ &dp(i-1,j,k,l-1)+t,\\ &dp(i,j-1,k,l-1)+t\\ &\} \end{align} $$
其中:
$$ t= \begin{cases} mat(i,j), i=k\;且\;j=l\\ mat(i,j)+mat(k,l), 其他 \end{cases} $$
遍历所有 $i,j,k,l$ 后,答案即为 $dp(N,N,N,N)$
- 时间复杂度:$O(N^4)$
- 空间复杂度:$O(N^4)$
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int MAXN = 15;
int mat[MAXN][MAXN];
int dp[MAXN][MAXN][MAXN][MAXN];
void solve()
{
int N;
cin >> N;
int a, b, c;
while (cin >> a >> b >> c, a || b || c)
mat[a][b] = c;
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= N; j++)
{
for (int k = 1; k <= N; k++)
{
for (int l = 1; l <= N; l++)
{
int t = mat[i][j];
if (i != k || j != l)
t += mat[k][l];
dp[i][j][k][l] = max({dp[i][j][k][l],
dp[i - 1][j][k - 1][l] + t,
dp[i - 1][j][k][l - 1] + t,
dp[i][j - 1][k - 1][l] + t,
dp[i][j - 1][k][l - 1] + t});
}
}
}
}
cout << dp[N][N][N][N] << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
优化维数
我们让两次取数同时进行,即两次取数的路径长一致,令路径长度为 $s$. 此处为了编程方便,点 $(x,y)$ 路径长度的含义为 $x+y$ 而非 $x+y-2$.
那么,记 $dp(s,i,k):=$ 两次取数分别为从 $(1,1),(1,1)$ 走到 $(i,s-i),(k,s-k)$ 的情况能取到的数字的和的最大值。
我们发现,之前四维才能表示的情况,现在用三维就能表示出现了,由此就省下了一个维度的复杂度。算法的其他部分一致。
- 时间复杂度:$O(N^3)$
- 空间复杂度:$O(N^3)$
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int MAXN = 15;
int mat[MAXN][MAXN];
int dp[2 * MAXN][MAXN][MAXN];
void solve()
{
int N;
cin >> N;
int a, b, c;
while (cin >> a >> b >> c, a || b || c)
mat[a][b] = c;
for (int s = 2; s <= 2 * N; s++)
{
for (int i = 1; i < s; i++)
{
for (int k = 1; k < s; k++)
{
int j = s - i, l = s - k;
int t = mat[i][j];
if (i != k || j != l)
t += mat[k][l];
dp[s][i][k] = max({dp[s][i][k],
dp[s - 1][i - 1][k - 1] + t,
dp[s - 1][i - 1][k] + t,
dp[s - 1][i][k - 1] + t,
dp[s - 1][i][k] + t});
}
}
}
cout << dp[2 * N][N][N] << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi