蓝桥杯——滑行(树形DP)
-
题目链接 https://www.lanqiao.cn/problems/2414/learning
-
题目理解:就是一个int dfs()问题,但是不加任何优化肯定会超时!mlgb,蓝桥杯好像压根不考那种简单数据结构题。四条路径用dfs遍历,记录层数,每次递归进行比较是否为最大层数。
-
优化:采用树形DP的思想,就是 从叶子结点开始往上面加层数,每次遇到层数已经被记录的点,直接返回,就不需要再看它的孩子结点(周围结点)。本题中就是遇到一个点就遍历它的周围结点,就递归,在下一次函数中判断点是否合法。递归函数最终返回的是该结点的最大滑行距离。从什么时候开始赋值呢?从叶结点,这里也就是终点,此时遍历周围四个点都不合法,返回0,那么每个递归函数里面的
int maxL = 0
,就仍然是0
,本结点的遍历仍然是0
,那么最终返回dp[x][y] = maxL + 1
为1
。返回到上一层的循环遍历中,此时上一层的maxL
就为1
了,如果恰巧上一层那个点周围全都是终点,那maxL就为1,最终d[x][y] = maxL + 1
为2
。依次递推,就好像是已知该点周围结点的最大滑行距离,取最大的,然后+1就得到该点的最大滑行距离距离。 -
注意:树形DP也算是一个非常规范的算法,很正规,目前没看到变种,一定要记住,并且一定要理解它的应用场合。 就比如这道题,就好像是求每个结点到叶结点的最大距离(叶结点到自己本身距离为1)。而且这道题不需要标记哪些点你已经访问过了,而是需要将所给图中的,全部点进行访问,如果该点的
dp[x][y]!=0
说明先前访问过了,那么这就直接返回,看这点是否是比目前得到的全局最大距离还大,如果是则替换,反之则不用改变。但是如果该点dp[x][y]==0
,说明没访问过,那就需要老老实实进行树形DP求解,中间可能遇到已知dp[][]
的点,直接返回最为此次循环与最大值maxL
比较的值。只要从一个点出发,树形DP遍历过的点都会求得dp[x][y]
这个值。 -
代码:
#include <bits/stdc++.h> using namespace std; /* 从前往后的思想是不行的,每次求得一个点的最大滑行距离再标记没什么用。 因为你就算遇到了这个点,你也要确定这个点是否应该是正确的,可能经过其他点的滑行距离最大呢? 这个思想一定要记住,每次到终点返回1,m=0 mx=1,而后自己本身又要经过为1,所以最大为2。 主要操作为一个max和mx+1 这种树形dfs一定要会。 */ int n, m, h[103][103], sum = 0, dp[103][103]; int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0}; int maxLength; // 传入的 x, y 为出发点或者经过点。 // 这样的写法可以得到每个点的最长距离。 int dfs(int x, int y) { if (x < 1 || x > n || y < 1 || y > m) return 0; if(dp[x][y]) return dp[x][y]; int maxL = 0; // 表示可走的方向为4个 for (int k = 0; k <= 3; k++) { // 换方向走 int tx = x + dx[k], ty = y + dy[k]; // 表示可以走 if (h[tx][ty] < h[x][y]) { maxL = max(maxL, dfs(tx, ty)); } } // maxL 表示先前点的最大值。树形DP!这就是啊! dp[x][y] = maxL + 1; return dp[x][y]; } int main() { int maxL = 0; cin >> n >> m; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cin >> h[i][j]; } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { maxL = max(maxL, dfs(i, j)); } } // cout << "dp动态规划矩阵为:" << endl; // for (int i = 1; i <= n; i++) // { // for (int j = 1; j <= m; j++) // { // cout << dp[i][j] << " "; // } // cout << endl; // } cout << maxL << endl; return 0; }