题目 | 方格取数
AcWing 1027. 方格取数
动态规划典型例题
https://www.acwing.com/problem/content/1029/
题目
设有
某人从图中的左上角
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字
此人从
输入格式
第一行为一个整数
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
行和列编号从
一行 “0 0 0” 表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
数据范围
输入样例
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
题解
能否分为两次取数?
一次取数的问题很好解决,该题的难点是进行了两次取数。需要注意的一点是,不可将两次取数分别处理,因为两次分别的最优操作在总体上并不一定是最优操作。如果这点无法理解,可以参考以下示例:
如果我们将其分为两次取数来思考,第一次取数时,最优策略如下,一共取到了
第二次取数时,最优策略如下,一共取到了
但是我们通过目测都能发现更优的取法(方框代表第一次取,圆括号代表第二次取),一共能取
这就是为什么两次最优不等于总体最优。
朴素解法
先考虑仅一次操作的情形,
我们从一次操作再推广到两次,令
此时,转移方式有四种,来源分别为:
- 第一次向下,第二次向下:
- 第一次向右,第二次向下:
- 第一次向下,第二次向右:
- 第一次向右,第二次向右:
转移后,我们需要加上本次取得数,此时就得考虑重复取数的情况:我们不能将一个数取两次
解决方法很简单,如果两次取的数相同,则只加一次,若不同,则加上取得两个不同的数:
- 若
且 : - 反之:
综上,转移方式即为:
其中:
遍历所有
- 时间复杂度:
- 空间复杂度:
#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;
}
优化维数
我们让两次取数同时进行,即两次取数的路径长一致,令路径长度为
那么,记
我们发现,之前四维才能表示的情况,现在用三维就能表示出现了,由此就省下了一个维度的复杂度。算法的其他部分一致。
- 时间复杂度:
- 空间复杂度:
#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