方格取数

洛谷 P1004 方格取数
题目描述

设有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?
这样就像贪心了,无法保证两次的和最大
因为时间对本题无影响,可以假设放出一个分身和他同时走,各走各的
那么当前最大值就是上一步最大值加上此时这人和他分身所处格子的数值
要是走重了怎么办?
相当于同一个格子取了两次,减掉一次就好了

  • 因为人和分身同时走,而且只能向右或向下
    所以他们走到同一个格子时步数一定是相同的qwq

下面给出代码及空间优化


分别记下他们的位置坐标,需要四维

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 15
using namespace std;
int n;
int p[N][N];
int f[N][N][N][N];
int main(){
    memset(f,0,sizeof(f));
    memset(p,0,sizeof(p));
    cin>>n;
    int r,c,v;
    do{
        cin>>r>>c>>v;
        p[r][c]=v;
    }while( r || c || v );
    for(int a=1;a<=n;++a)
    {
        for(int b=1;b<=n;++b)
        {
            for(int c=1;c<=n;++c)
            {
                for(int d=1;d<=n;++d)
                {
                    f[a][b][c][d]=max(f[a][b][c][d],f[a][b-1][c][d-1]);
                    f[a][b][c][d]=max(f[a][b][c][d],f[a][b-1][c-1][d]);
                    f[a][b][c][d]=max(f[a][b][c][d],f[a-1][b][c][d-1]);
                    f[a][b][c][d]=max(f[a][b][c][d],f[a-1][b][c-1][d]);
                    //上一次的选择同右、右下、同下、下右
                    f[a][b][c][d]+=(p[a][b]+p[c][d]);
                    if(a==c && b==d)
                        f[a][b][c][d]-=p[a][b];//重了减掉
                }
            }
        }
    }
    cout<<f[n][n][n][n]<<endl;
    return 0;
}

i为当前步数,xy为坐标
x+y=i+2

  • 简单证明:
    A(1,1)x+y=2 i=0
    之后每走一步(不论向右向下)x+y加一
    步数为ix+y+=ix+y=i+2

y=i+2-x

这样只要记步数和横坐标,共三维

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 15
using namespace std;
int n;
int a[N][N];
int f[2*N][N][N];
int main(){
    memset(f,0,sizeof(f));
    memset(a,0,sizeof(a));
    cin>>n;
    int r,c,v;
    do{
        cin>>r>>c>>v;
        a[r][c]=v;
    }while( r || c || v );
    f[0][1][1]=a[1][1];//不初始化会死得很惨 (1,1)上可能有值
    for(int i=1;i<=2*n-2;++i)//从A到B要2*n-2步
    {
        for(int x1=1;x1<=i+1;++x1)//y=i+2-x>=1 -> x<=i+1
        {
            for(int x2=1;x2<=i+1;++x2)
            {
                int y1=i+2-x1,y2=i+2-x2;
                f[i][x1][x2]=max(f[i][x1][x2],f[i-1][x1][x2]);
                f[i][x1][x2]=max(f[i][x1][x2],f[i-1][x1-1][x2]);
                f[i][x1][x2]=max(f[i][x1][x2],f[i-1][x1][x2-1]);
                f[i][x1][x2]=max(f[i][x1][x2],f[i-1][x1-1][x2-1]);
                f[i][x1][x2]+=(a[x1][y1]+a[x2][y2]);
                if(x1==x2 && y1==y2)
                    f[i][x1][x2]-=a[x1][y1];
            }
        }
    }
    cout<<f[2*n-2][n][n]<<endl;
    return 0;
}

f[i]只与f[i-1]有关
so存两张表就好,滚动数组实现

代码走丢了.cpp

其实跟01背包很像
上一维的不用存,倒序更新就好

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 15
using namespace std;
int n;
int a[N][N];
int f[N][N];
int main(){
    memset(f,0,sizeof(f));
    memset(a,0,sizeof(a));
    cin>>n;
    int r,c,v;
    do{
        cin>>r>>c>>v;
        a[r][c]=v;
    }while( r || c || v );
    f[1][1]=a[1][1];
    for(int i=1;i<=2*n-2;++i)
    {
        for(int x1=i+1;x1>=1;--x1)
        {
            for(int x2=i+1;x2>=1;--x2)
            {
                int y1=i+2-x1,y2=i+2-x2;
                f[x1][x2]=max(f[x1][x2],f[x1][x2]);
                f[x1][x2]=max(f[x1][x2],f[x1-1][x2]);
                f[x1][x2]=max(f[x1][x2],f[x1][x2-1]);
                f[x1][x2]=max(f[x1][x2],f[x1-1][x2-1]);
                f[x1][x2]+=(a[x1][y1]+a[x2][y2]);
                if(x1==x2 && y1==y2)
                    f[x1][x2]-=a[x1][y1];
            }
        }
    }
    cout<<f[n][n]<<endl;
    return 0;
}

本文思路借鉴: 以墨

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值