[线性dp] 方格取数(数字三角形模型)

0. 前言

相关:

[线性dp] 数字三角形(模板+数字三角形模型)

[线性dp] 摘花生(数字三角形模型)

1. 数字三角形+模板题

1027. 方格取数

在这里插入图片描述

理解:[线性dp] 摘花生(数字三角形模型)向下、向右每次只走一次,到达右下角即终止,即一条路线。本题也是只能向下、向右走,但是需要走两次,即两条路线当两条路线走到了相同格子,则当前格子只进行一次的累加(这个对应到题目来讲就是,一条路径走过使得当前格子变为 0)针对不同格子就进行两次的累加。

重点: 线性 dp、数字三角形模型

思路:

  • 状态定义:
    • 摘花生走一次用 f[i][j] 表示其中一条路线,针对走两次可做简单拓展
    • f[i1][j1][i2][j2] 表示从起点 [1,1][1,1] 走到 [i1,j1][i2,j2] 点的所有路径和的最大值
    • i1+j1=i2+j2=k 时,这两条路径才可能会走到同一个格子中。k 表示两条路线当前走到的格子横纵坐标之和
    • i1+j1=i2+j2 相等时,两条路线不一定走到同一格子中,但是在同一格子中的话,即有 i1=i2,一定满足 i1+j1=i2+j2。所以,我们可以枚举 k、i1、i2,此时 j1=k-i1j2=k-i2,当 i1=i2 时,针对同一个 k 则必定有 j1=j2 就能够判断出是处于相同格子下,这样就能简化一维的枚举
    • 故,状态可定义为 f[k][i1][i2] 表示所有从起点 [1,1][1,1] 走到 [i1,k-i1][i2,k-i2] 点的所有路径和的最大值
  • 状态转移:
    • 针对两条路径,其分别只能走向 下、右 这两个方向,那么针对 f[k][i1][i2] 状态的转移就有以下 4 种情况
    • 1下2下:最后一步是向下走才到达 f[k][i1][i2] 点的。那么显然前一个状态的最大值就是 f[k-1][i1-1][i2-1],当两条路径没有走到同一个格子的时候,我们需要加上 w[i1][j1]+w[i2][j2],当两条路径走到同一个格子的时候,我们只能加一个格子,相当于另一个格子加 0,在此即为 w[i1][j1]
    • 1下2右、1右2下、1右2右 分析方式同理
    • 状态转移方程: 首先枚举 k,从 2~n+n 枚举,然后枚举 i1i2。当在一个 k 下有 i1 = i2 则说明是重合格子。则只用加一遍当前格子,否则需要将不同的两个格子都进行状态累加,转移
      int t = w[i1][j1];
      if (i1 != i2) t += w[i2][j2];			// 如果不是同一个格子,则累加
      f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2 - 1] + t);			// 四种情况的状态转移,下下
      f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2] + t);				// 下右
      f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2 - 1] + t);				// 右下
      f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2] + t);					// 右右
      
  • 边界情况及初始化:
    • 全局数组自动初始化为 0,取 max 操作,不需要初始化其它特殊位置
    • 枚举一定要想清楚,先枚举横纵坐标之和 kk,再配合上两个路径的横坐标 i1、i2 就能判断是否为重合路径。这点很重要i1+j1=i2+j2=k 不代表它一定是重合格子,但是重合格子一定满足该式。当 i1=i2,则对同一个 k,也必定满足 j1=j2 则为同一个格子。这点转化很是巧妙
    • 答案即为:f[n+n][n][n]

2000 年 NOIP 提高组的 DP 题目,很有意思。是摘花生的加强版,针对相同格子判断这个操作,挺秀的,实力用 k 简化掉了一维的枚举。

当然,这玩意还能拓展到 k 种路径。这已经无法拿 dp 来解来了。貌似是最小费用流问题,属于图论问题了。

写法一代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 15;

int n;
int w[N][N];
int f[N + N][N][N];

int main() {
    cin >> n;
    
    int a, b, c;
    while (cin >> a >> b >> c, a || b || c) w[a][b] = c;
    
    for (int k = 2; k <= n + n; ++k)
        for (int i1 = 1; i1 <= n; ++i1)
            for (int i2 = 1; i2 <= n; ++i2) {
                int j1 = k - i1, j2 = k - i2;
                if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n) {
                    int t = w[i1][j1];
                    if (i1 != i2) t += w[i2][j2];
                    f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2 - 1] + t);
                    f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2] + t);
                    f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2 - 1] + t);
                    f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2] + t);
                }
            }
    
    cout << f[n + n][n][n] << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

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

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

打赏作者

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

抵扣说明:

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

余额充值