方格取数问题--题解

方格取数问题题解

 

问题描述:

在一个二维的数字矩阵a[x][y]中,从矩阵的左上角的数字开始,每次只能向右或向下走一个格,每经过一个方格,便取走方格上的数字,问走的那条路线上的所有元素的最大和是多少?

 

思路:这是一个可用动态规划解决的问题,首先建立动态方程,用f(i,j)表示走到第i行第j列时的所有元素的最大值。那么,f(i,j)=max( f(i-1,j)  ,  f(i,j-1) )+a[i][j];当然,在i==0&&j!=0时,f(i,j)=f(i,j-1)+a[i][j];在i!=0&&j==0时,f(i,j)=f(i-1,j)+a[i][j];在i==0&&j==0时,f(i,j)=a[i][j]。

 

算法:用一个二维数组f[x][y]去存储状态f(i,j)的值,然后按照状态方程给f[x][y]赋值,最后去f数组中右下角的数字即可。

 

代码如下:

#include<cstdio>

#include<cstring>

int a[1000][1000];

int b[1000][1000];

int max(int x,int y){

    return x>y?x:y;

}

int main(){

    //memset(a,0,sizeof(a));

    memset(b,0,sizeof(b));

    int n,m;

    scanf("%d%d",&n,&m);//输入,二维矩阵的行数与列数

    for(int i=0;i<n;i++){

        for(int j=0;j<m;j++){

            scanf("%d",&a[i][j]);//输入元素

        }

    }

    for(int i=0;i<n;i++){

        for(int j=0;j<m;j++){//根据状态方程给数组b中的元素赋值

            if(i==0&&j==0){b[i][j]=a[i][j];}

            if(i==0&&j!=0){

                b[i][j]=a[i][j]+max(0,b[i][j-1]);

            }

            if(i!=0&&j==0){

                b[i][j]=max(b[i-1][j],0)+a[i][j];

            }

            if(i!=0&&j!=0){

                b[i][j]=max(b[i-1][j],b[i][j-1])+a[i][j];

            }

        }

    }

    printf("%d\n",b[n-1][m-1]);//去最后的一个元素即可

    return 0;

}

 

问题扩展1:(前后走两次求最大和)

假设要从左上角方格到右下角方格之间走两次,而且两次的路线可以走相同的方格,问这两条路线上的元素的最大值是多少?

思路:依然动归。考虑两条路线同时走,设res[i][j][ii][jj]是第一条路线达到(i, j)时、第二条路线到达(ii, jj)时,两条路线上元素的最大值。由于每个方格的走法只有“向下”、“向右”两种,因此,两条路线同时走的情况下res[i][j][ii][jj]的上一个状态一共有4种,分别是:res[i - 1][j][ii - 1][jj]、res[i - 1][j][ii][jj - 1]、res[i][j - 1][ii - 1][jj]、res[i][j - 1][ii][jj - 1],因此当前状态需要取上一步状态的最大值即可,于是状态转移方程是:res[i][j][ii][jj] = max(res[i - 1][j][ii - 1][jj], res[i - 1][j][ii][jj - 1], res[i][j - 1][ii - 1][jj],res[i][j - 1][ii][jj - 1]) + m[i][j] + m[ii][jj]。但是,由于当(i == ii && ii == jj)的时候,该方格的数值只能被加到一条路线上,于是需要多做一步减法:-m[i][j]。

代码如下:

#include<stdio.h>
#include<string.h>

int m[20][20];
int res[20][20][20][20];
int N;

#define max(a,b) \
    (a > b ? a : b)

int main(int argc, char *argv[]){
    scanf("%d", &N);
    int a, b, c;
    memset(m, 0x0, sizeof(m));
    memset(res, 0x0, sizeof(res));
    while(scanf("%d%d%d", &a, &b, &c)){
        if(a == 0 && b == 0 && c == 0){
            break;
        }
        m[a][b] = c;
    }
    int i, j, ii, jj;
    for(i = 1; i <= N; i ++){
        for(j = 1; j <= N; j ++){
            for(ii = 1; ii <= N; ii ++){
                for(jj = 1; jj <= N; jj ++){
                    res[i][j][ii][jj] = max(max(res[i - 1][j][ii - 1][jj], res[i - 1][j][ii][jj - 1]),
                                        max(res[i][j - 1][ii - 1][jj], res[i][j - 1][ii][jj - 1]))
                                            + m[i][j] + m[ii][jj];
                    if(i == ii && j == jj){
                        res[i][j][ii][jj] -= m[i][j];
                    }
                }
            }
        }
    }
    printf("%d\n", res[N][N][N][N]);
    return 0;
}

 

问题拓展2:(记忆化搜索)

如果要求走两条互不相交的路线都到达右下角的位置,问这两条路线上的元素的最大值是多少?

 

思路:如果想要走两条路线,并且要去求两条路线的所有元素的最大值,而且两条路线互不相交,因此,只有当每条路线在各自的情况下都达到最优时,两条路线上的所有元素就是最大的。因此,当地一条路线遍历完,寻找到最大值路线时,我们需要将其路线上的每个元素都标记一下或者将其置为零,然后按相同的办法去规划另一条路线即可。(局部最优不代表全局最优,该问题暂时没有想到其他有效的解法,待续……)

 

算法:使用上面的数组b存储f(i,j)后,在b中倒叙搜索最大路径,并将路径上的每个点都置为-1。(如果想要输出路径,便可以将其置成其他的特征值,以便于搜索路径。)然后,再进行一次遍历,但是a[i][j]=-1的点不遍历,更新数组b的值。使用sum存储两次遍历的最大值之和即可。

 

代码如下:

#include<cstdio>

#include<cstring>

int a[1000][1000];

int b[1000][1000];

 

int max(int x,int y){

    return x>y?x:y;

}

void find(int x,int y){

    a[x][y]=-1;

    if(x!=0||y!=0){

        if(x==0&&y!=0){find(x,y-1);}

        else if(x!=0&&y==0){find(x-1,y);}

        else{

            if(b[x-1][y]>b[x][y-1])find(x-1,y);

            else find(x,y-1);

        }

    }

}

int main(){

    memset(a,0,sizeof(a));

    memset(b,0,sizeof(b));

    int n;

    scanf("%d",&n);

    for(int i=0;i<n;i++){

        int x,y;

        scanf("%d%d",&x,&y);

        scanf("%d",&a[x-1][y-1]);

    }

    

    for(int i=0;i<n;i++){

        for(int j=0;j<n;j++){

            if(i==0&&j==0){b[i][j]=a[i][j];/*a[i][j]=0;*/}

            if(i==0&&j!=0){

                b[i][j]=a[i][j]+max(0,b[i][j-1]);

                //a[i][j]=0;

            }

            if(i!=0&&j==0){

                b[i][j]=max(b[i-1][j],0)+a[i][j];

            }

            if(i!=0&&j!=0){

                b[i][j]=max(b[i-1][j],b[i][j-1])+a[i][j];

            }

        }

    }

    //printf("%d\n",b[n-1][n-1]);

    int sum=b[n-1][n-1];

   // printf("%d\n",sum);

    find(n-1,n-1);

    memset(b,0,sizeof(b));

    for(int i=0;i<n;i++){

        for(int j=0;j<n;j++){

            if(i==0&&j==0){b[i][j]=a[i][j];/*a[i][j]=0;*/}

            if(i==n-1&&j==n-1)b[i][j]=max(b[i-1][j],b[i][j-1]);

            if(a[i][j]!=-1){

                if(i==0&&j!=0){

                    b[i][j]=a[i][j]+max(0,b[i][j-1]);

                    //a[i][j]=0;

                }

                if(i!=0&&j==0){

                    b[i][j]=max(b[i-1][j],0)+a[i][j];

                }

                if(i!=0&&j!=0){

                    b[i][j]=max(b[i-1][j],b[i][j-1])+a[i][j];

                }

            }

            

        }

    }

    sum+=b[n-1][n-1];

    printf("%d\n",sum);

    return 0;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值