Acwing算法提高 -- 动态规划 -- 数字三角形模型

一、数字三角形模型的概念

模型概述

类似于一种路线的行走,一般来说题意都是说从起点到终点的最大/小权值,该权值定义根据题意设置。

下面是一道比较典型且简单的入门题,能够帮助我们了解根据该模型设置的题目类别。

Acwing 1015 摘花生

题目分析

这题抽象出来它的本质就是从左上角走到右下角获得的最大权值。

用闫式DP分析法来显示:

解题过程

根据闫式DP分析法我们已经有了状态转移方程,因此进行如下操作:

Step 1.将输入存储于g[N][N]中,g[i][j]表示第(i, j)处花生的数量;

Step 2.用f[N][N]来表示状态转移数组,其含义在上图中有所显示;

Step 3.枚举出图中所有位置,按照上述状态转移进行操作;

Step 4.输出f[n][m]就是答案

代码展示

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int T, n, m;
int g[N][N], f[N][N];
int main() {
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                cin >> g[i][j];
            }
        }
        memset(f, 0, sizeof f);
        for (int i = 0; i < m; i++) f[0][i] = f[0][i - 1] + g[0][i];
        for (int i = 1; i < n; i++) f[i][0] = f[i - 1][0] + g[i][0];
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < m; j++) {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]) + g[i][j];
            }
        }
        cout << f[n - 1][m - 1] << endl;
    }
    return 0;
}

这样我们就顺利解决了这道问题!

然而,仅仅只是这样那也太简单了,因此来上一点难度,我们刚刚解决的仅仅是一条路,那如果此时让我们找出和最大的两条路,又该怎么做呢?

这里有一道例题:Acwing 275 传纸条

按照我们刚才的做法,会联想到:我找两次最大路不就行了?

这种想法是错误的,因为我们所求的是最大和,找两次无法保证和最大(因为5(第一次) + 5(第二次) > 6(第一次) + 2(第二次)的情形会出现)

因此我们这里有一种新的做法,还是上闫式DP分析法图片:

对于这个分析我们对于其中的疑问进行解答:

1.为什么要设置成f[s][i][j]?

我们私下手动画一下图就能发现,从(x1, y1)(x2, y2)无论其路线怎么走,其总路程是一样的,也就是distance_red == distance_blue

有了这个性质就好办了,推广一下从(1, 1)(n, m)就知道两条起点相同终点相同的路线在限制范围内路程总长度是一样的

又因为路程总长度distance_total = distance_first + distance_second,所以我们这样定义状态转移数组。

接下来要注意,图中粉色的点是两条路线的交汇处,把思路拓展到把整个图当作一个状态

我们从小的网格范围拓展到整个网格图,也是一种状态转移的表现

最后我们的答案f[n][m]一定是从以先前的交汇点作为终点的状态累积转移而来。

2.这个状态划分是怎么来的?

这个状态划分和最初那道题的思路就差不多了,以最后一步(假设最后一步到(i, j))如何移动划分集合,一共有四种情形:

1.路线1从上边到(i, j),路线2从左边到(i, j)

2.路线1从左边到(i, j),路线2从上边到(i, j)

3.路线1从上边到(i, j),路线2从上边到(i, j)

4.路线1从左边到(i, j),路线2从左边到(i, j)

代码展示

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 60;
int n, m;
int g[N][N];
int f[N * 2][N][N];
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> g[i][j];
        }
    }
    for (int s = 2; s <= n + m; s++) {
        for (int i = max(1, s - m); i <= min(n, s - 1); i++) {
            for (int j = max(1, s - m); j <= min(n, s - 1); j++) {
                int x = g[i][s - i];
                if (i != j) x += g[j][s - j];
                int& v = f[s][i][j];
                for (int x1 = 0; x1 <= 1; x1++) {
                    for (int x2 = 0; x2 <= 1; x2++) {
                        v = max(v, f[s - 1][i - x1][j - x2] + x);
                    }
                }
            }
        }
    }
    cout << f[m + n][n][n] << endl;
    return 0;
}

最后留一个练习题:Acwing 1027 方格取数

posted @ 2024-05-20 18:20  uiwhiuhweur  阅读(5)  评论(0)    收藏  举报