题解:洛谷 P1004 方格取数
【题目来源】
洛谷:P1004 [NOIP 2000 提高组] 方格取数 - 洛谷
【题目描述】
设有 \(N\times N\) 的方格图(\(N\le 9\)),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 \(0\)。如下图所示(见样例):

某人从图的左上角的 \(A\) 点出发,可以向下行走,也可以向右走,直到到达右下角的 \(B\) 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 \(0\))。
此人从 \(A\) 点到 \(B\) 点共走两次,试找出 \(2\) 条这样的路径,使得取得的数之和为最大。
【输入】
输入的第一行为一个整数 \(N\)(表示 \(N\times N\) 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 \(0\) 表示输入结束。
只需输出一个整数,表示 \(2\) 条路径上取得的最大的和。
【输出】
只需输出一个整数,表示 \(2\) 条路径上取得的最大的和。
【输入样例】
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
【核心思想】
-
问题分析:给定 \(N \times N\) 方格图,从左上角 \((1,1)\) 到右下角 \((N,N)\) 走两次(每次只能向下或向右),取走经过方格中的数(取走后变为 \(0\))。求两条路径取得的最大数字之和。这是一个四维线性 DP 问题,核心在于将两条路径同步处理,状态记录两条路径的当前位置。
-
算法选择:
- 四维 DP:\(dp[i][j][k][l]\) 表示第一条路径走到 \((i,j)\)、第二条路径走到 \((k,l)\) 时的最大和
- 同步推进:两条路径同时从 \((1,1)\) 出发,每步各向下或向右走一格,因此 \(i+j = k+l\)(步数相同)
-
关键步骤:
- 初始化:读取 \(N\) 和网格数据 \(a[x][y]\),\(dp\) 数组初始化为 \(0\)
- 四维 DP 递推(\(i, j, k, l\) 从 \(1\) 到 \(N\)):
- 仅当 \(i + j = k + l\) 时处理(保证两条路径步数同步)
- 状态转移:两条路径上一步各有 \(2\) 种选择(从上方或左方来),共 \(4\) 种组合:
- 路径1从 \((i-1,j)\) 来,路径2从 \((k-1,l)\) 来:\(dp[i-1][j][k-1][l]\)
- 路径1从 \((i-1,j)\) 来,路径2从 \((k,l-1)\) 来:\(dp[i-1][j][k][l-1]\)
- 路径1从 \((i,j-1)\) 来,路径2从 \((k-1,l)\) 来:\(dp[i][j-1][k-1][l]\)
- 路径1从 \((i,j-1)\) 来,路径2从 \((k,l-1)\) 来:\(dp[i][j-1][k][l-1]\)
- \(dp[i][j][k][l] = \max(\text{四种组合}) + a[i][j] + a[k][l]\)
- 去重:若 \((i,j) = (k,l)\)(两条路径重合),该格子的数只取一次,减去 \(a[k][l]\)
- 输出答案:\(dp[N][N][N][N]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N^4)\),四层循环,\(N \leq 9\) 时完全可接受
- 空间复杂度:\(O(N^4)\),四维 DP 数组
-
线性 DP 的核心思想:
- 同步状态设计:将两条路径的行走过程同步化,用四维状态同时记录两条路径的位置,避免分别计算后处理重叠的复杂性
- 步数一致性约束:\(i+j = k+l\) 确保两条路径走了相同的步数,这是同步推进的关键
- 四种转移全覆盖:每条路径各有 \(2\) 种来向,组合成 \(4\) 种转移,取最大值保证最优性
- 重叠处理:两条路径经过同一格子时,该格子的值只应被计算一次,通过判断 \((i,j) = (k,l)\) 并减去重复值实现
- 降维优化:可利用 \(i+j = k+l = s\) 将四维降为三维 \(dp[s][i][k]\),但四维写法更直观
- 适用于多路径同步、网格行走、重叠去重类 DP 问题
【解题思路】

【算法标签】
普及+ #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int a[15][15]; // 存储网格中的数值
int dp[15][15][15][15]; // 四维DP数组,记录两条路径的最大和
int n, x, y, num; // n: 网格大小,x/y: 坐标,num: 数值
/**
* 辅助函数:返回四个数中的最大值
*/
int f(int a, int b, int c, int d)
{
return max(max(a, b), max(c, d));
}
int main()
{
// 输入网格大小n
cin >> n;
// 输入网格数据,直到遇到x=0结束
while (cin >> x >> y >> num && x)
{
a[x][y] = num;
}
// 动态规划计算两条路径的最大和
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++)
{
// 确保两条路径同步前进(i+j == k+l)
if (i + j == k + l)
{
// 状态转移:取四种可能的最大值加上当前两个点的值
dp[i][j][k][l] = f(dp[i-1][j][k-1][l],
dp[i-1][j][k][l-1],
dp[i][j-1][k-1][l],
dp[i][j-1][k][l-1])
+ a[i][j] + a[k][l];
// 如果两条路径重合,减去重复计算的值
if (i == k && j == l)
{
dp[i][j][k][l] -= a[k][l];
}
}
}
}
}
}
// 输出最终结果(原代码缺少输出语句,这里保持原样)
return 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
浙公网安备 33010602011771号