方格取数 动态规划

个人题解记录

方格取数 动态规划

资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
  设有 NN 的方格图(N<=10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0。
  某人从图的左上角的 A 点(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的 B 点(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
  此人从 A 点到 B 点共走两次,试找出 2 条这样的路径,使得取得的数之和为最大。
输入格式
  输入的第一行为一个整数 N(表示 N
N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 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取走,格子上的值会变为0B路径再次经过就取了个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 and 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-1][h-1][k], dp[i][j-1][h][k-1]) + value[i][j]

  • 当甲乙不同位置,i!=h and 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-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;
}

若有不对的地方,望指点

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会的二愣子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值