洛谷P1004方格取数(动态规划+DP+递推)


       递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处。

题目

       设有 N×N 的方格图 (N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0。如下图所示(见样例):

A
0 0 0 0 0 0 0 0
0 0 13 0 0 6 0 0
0 0 0 0 7 0 0 0
0 0 0 14 0 0 0 0
0 21 0 0 0 4 0 0
0 0 15 0 0 0 0 0
0 14 0 0 0 0 0 0
0 0 0 0 0 0 0 0
B
       某人从图的左上角的 A 点出发,可以向下行走,也可以向右走,直到到达右下角的 B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
       此人从 A点到 B点共走两次,试找出 2 条这样的路径,使得取得的数之和为最大。

输入格式
       输入的第一行为一个整数 N(表示 N×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 表示输入结束。

输出格式
       只需输出一个整数,表示 2 条路径上取得的最大的和。

过程

首先先过一遍题目——经典的dp,可能是二维。
但是仔细瞧瞧——要走两次!
要走两次怎么办?想象成两个人走呀!
那么,自然就是四维dp啦!

假象两个人分别走,一个人走到dp[i][j],获得了最大值,另一个则是另一个人走到dp[k][l],获得的最大值,这两个人收集的和,就是dp[i][j][k][l]值了。

太棒啦!转移方程自然就出来啦~

dp[i][j][k][l]=max(max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))+a[k][l]+a[i][j];

稍等,如果两个人撞上了怎么办?

试找出2条这样的路径,使得取得的数之和为最大。

咬文嚼字一下,发现题目说的是这两条路径取得的数的和,主语是取得的数,并不是两条路径。所以,这两条路径有交集的话,和只能加一遍。

那怎么办?
把方程改进一下:

dp[i][j][k][l]=max(max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))+(((i==k&&j==l)?0:(a[k][l]))+a[i][j]);

这样,如果i==k&&j==l,即两个人撞上了,就只加一次

代码

方法一:

#include <stdio.h>
int a[10][10],dp[10][10][10][10];
int tmpx,tmpy,tmpnum,n;
int max(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    scanf("%d",&n);
    while(1)
    {
        scanf("%d%d%d",&tmpx,&tmpy,&tmpnum);
        if(tmpx==0&&tmpy==0&&tmpnum==0) break;
        a[tmpx][tmpy]=tmpnum;
    }
    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++)
                    dp[i][j][k][l]=max(max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))+(((i==k&&j==l)?0:(a[k][l]))+a[i][j]);
    printf("%d\n",dp[n][n][n][n]);
    return 0;
}

方法二(滚动方程):

#include<stdio.h>
#include<string.h>
const int INF = 999999999;
int dp[2][12][12],p[12][12];
int max(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    int n,i,j,k,a,b,c,x;
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        memset(p,0,sizeof(p));
        while(1)
        {
            scanf("%d%d%d",&a,&b,&c);
            if(a==0&&b==0&&c==0)break;
            p[a][b]=c;
        }
        int t=0;
        for(k=1;k<=2*n;k++)
        {
            t=1-t;
            for(i=1;i<=k;i++)
                for(j=1;j<=k;j++)
            {
                x=-INF;//无穷小
                x=max(x,dp[1-t][i][j-1]);
                x=max(x,dp[1-t][i-1][j]);
                x=max(x,dp[1-t][i-1][j-1]);
                x=max(x,dp[1-t][i][j]);
                if(i==j)
                    dp[t][i][j]=x+p[i][k+1-i];
                else dp[t][i][j]=x+p[i][k+1-i]+p[j][k+1-j];//加1是因为j从1开始
            }
        }
        printf("%d\n",dp[t][n][n]);
    }
    return 0;
}

方法三:

#include<stdio.h>
int max(int a,int b)
{
    return a>b?a:b;
}
struct point
{
    int x,y,data;//记录每个点的位置和数值
}p[100];
int n,m=0,map[11][11],f[11][11];
int main()
{
    int i,ii,j,jj,l;
    scanf("%d",&n);
    while(1)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        if(!a&&!b&&!c)
            break;
        p[++m].x=a;
        p[m].y=b;
        p[m].data=c;
    }
    for(i=1;i<=m;i++)
        map[p[i].x][p[i].y]=p[i].data;
        
    for(l=2;l<=n*2;l++)//每个点最少横着竖着都走一格,最多都走2n格就到终点
        for(i=l-1;i>=1;i--)//和前面说的一样,倒着做
            for(ii=l-1;ii>=1;ii--)
            {
                j=l-i;jj=l-ii;//i+j=ii+jj=l
                f[i][ii]=max(max(f[i][ii],f[i-1][ii-1]),max(f[i-1][ii],f[i][ii-1]))+map[i][j];
//重点说明一下吧,这里省略了很多。如果i不减1,意思就是j-1,因为上一个阶段就是l-1嘛。如果ii-1,意思就是说jj不减1。
                f[i][ii]+=map[ii][jj]*(i!=ii);
//如果i==ii,其实就是(i==ii&&j==jj),因为和都是l嘛。如果走过一遍,第二遍走得到的值就是0(题目上说的)。
            }
    printf("%d\n",f[n][n]);
//输出意思是在路径长度为2*n的阶段,两遍都走到(n,n)的最优值。因为在这里(j=2*n-i=n,jj=2*n-ii=n),所以走到的就是(n,n)的位置
    return 0;
}

数组的初始化

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值