个人题解记录
方格取数 动态规划
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
设有 NN 的方格图(N<=10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0。
某人从图的左上角的 A 点(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的 B 点(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
此人从 A 点到 B 点共走两次,试找出 2 条这样的路径,使得取得的数之和为最大。
输入格式
输入的第一行为一个整数 N(表示 NN 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 表示输入结束。
输出格式
只需输出一个整数,表示 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*N的网格中取数,左上角(1,1)出发,到达右下角(N*N)。行走的方向只能向右或向下,经过的格子上的数会被取走。找出两条路径,使取得的数之和最大。
解题思路
此题类似于迷宫,第一反应则是dfs,遍历每一条路,但搜索的代价太大了,并且题意的目的是要找到数值之和最大的两条。
首先想,“要如何才能使得和最大”,如下图。
在图上很容易看出到达(2,2)
的最大路径的值之和为5
,为什么是5
呢?因为方向只能往右或往下,那么要到达(2,2)
无非就是来自上方或左方,这样就只需要对比上方和左方哪边的路径的值之和最大。
这样看来这就是一个用动态规划
来解决的题。现在唯一的问题是,题目要求找到两条这样的路径,但一个格子的数只能被取一次,也就是说A路径
经过了(i,j)
,则(i,j)
的值被A取走,格子上的值会变为0
,B路径
再次经过就取了个0
。对于这个问题有两种解决方案。
方案一
以题给出的样例为例
要从A点到达B点,甲乙两个人同时从A点出发,用一个四维数组 dp[i][j][h][k] 来表示,(i,j)表示甲在格子上对应的位置,(h,k)表示乙在格子上对应的位置。
对于到达dp[i][j][h][k]这个位置有4种情况
1.甲来自上方,乙来自上方(上上)
2.甲来自上方,乙来自左方(上左)
3.甲来自左方,乙来自上方(左上)
4.甲来自左方,乙来自左方(左左)
只需要比较4种情况的值,取最大的。再加上一个关键点,一个位置的值只能被取一次,那么当甲乙走到同一个位置时,只需要加格子上的值即可。当在不同位置,那两个位置的值都得加上。
这样一来就可以构造递推式了
- 当甲乙同位置,
i==h
andj==k
dp[i][j][h][k] = max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1], dp[i][j-1][h-1][k], dp[i][j-1][h][k-1]) + value[i][j]
- 当甲乙不同位置,
i!=h
andj!=k
dp[i][j][h][k] = max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1], dp[i][j-1][h-1][k], dp[i][j-1][h][k-1]) + value[i][j] + value[h][k]
原问题的解取dp[n][n][n][n]
源代码:
#include<iostream>
#include<algorithm>
using namespace std;
int dp[11][11][11][11]={0};
int main()
{
int n;
int value[11][11]={0};
int a, b, c;
cin >>n;
while(1)
{
cin >>a>>b>>c;
value[a][b]=c;
if(a==0&&b==0&&c==0) break;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int h=1;h<=n;h++)
{
for(int k=1;k<=n;k++)
{
if(i==h&&j==k)
{
dp[i][j][h][k]=max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1]);
dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h-1][k]);
dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h][k-1]);
dp[i][j][h][k] += value[i][j];
}else{
dp[i][j][h][k]=max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1]);
dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h-1][k]);
dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h][k-1]);
dp[i][j][h][k] += (value[i][j]+value[h][k]);
}
}
}
}
}
cout << dp[n][n][n][n] << endl;
return 0;
}
方案二
第一种方案是同时走,那第二种就是分开走,甲先走一遍,找到一条和值最大的路径,再让乙走。这样的话数组只需要定义二维的即可。
递推式: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + value[i][j]
现在的问题是:“我怎么知道甲走了哪条路呢”。
我们都知道,(i,j)格子只能从上方或左方到达,那不妨记录一下路径从哪而来就行。也就是说,只需定义一个route[][]数组,route[i][j]表示到达格子(i,j)是从上方还是左方来的,规定用U 表示来自上方,用L 表示来自左方。
(i,j)是从 (i-1,j) 来的,那么在route[i][j]记录一个 U。
由图中例子可知,到达(2,2)最大路径和为12,其路径是从(1,1)-(1,2)-(2,2),则route[2][2]=‘U’。当到达(n,n)后,记录第一趟的最大路径和,再从route[n][n]入手,找到这条路径,将路径上的值全赋为0。然后再走第二趟,把两趟的解加起来即为答案。
源代码:
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int N;
int ans;
int value[11][11]={0};
int dp[11][11]={0};
char route[11][11];
cin >> N;
for(;;)
{
int a,b,c;
cin >> a >> b >> c;
if(a==0&&b==0&&c==0) break;
value[a][b]=c;
}
// 走第一躺
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N;j++)
{
if(j==1){ // 当位置在第一列,只能从上方来
dp[i][j] = dp[i-1][j] + value[i][j];
route[i][j] = 'U';
}else if(i==1){ // 当位置在第一行,只能从左方来
dp[i][j] = dp[i][j-1] + value[i][j];
route[i][j] = 'L';
}
else if(dp[i-1][j]>=dp[i][j-1]){
dp[i][j] = dp[i-1][j] + value[i][j];
route[i][j] = 'U';
}else if(dp[i-1][j]<dp[i][j-1]){
dp[i][j] = dp[i][j-1] + value[i][j];
route[i][j] = 'L';
}
}
}
ans = dp[N][N]; // 记录第一躺的解
int x=N; // 行号,当来自上方,行号减一
int y=N; // 列号,当来自左方,列好减一
while(x>=1&&y>=1)
{
if(route[x][y]=='U') x--;
else if(route[x][y]=='L') y--;
value[x][y]=0; // 将经过的格子全赋值为0
}
// 走第二趟
dp[11][11] = {0};
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N;j++)
{
if(j==1){
dp[i][j] = dp[i-1][j] + value[i][j];
}else if(i==1){
dp[i][j] = dp[i][j-1] + value[i][j];
}
else if(dp[i-1][j]>=dp[i][j-1]){
dp[i][j] = dp[i-1][j] + value[i][j];
}else if(dp[i-1][j]<dp[i][j-1]){
dp[i][j] = dp[i][j-1] + value[i][j];
}
}
}
cout << ans+dp[N][N] << endl; // 两趟的结果相加
return 0;
}
若有不对的地方,望指点