##[$AcWing$ $898$. 数字三角形](https://www.acwing.com/problem/content/description/900/) 给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。 ```cpp {.line-numbers} 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 ``` **输入格式** 第一行包含整数 $n$,表示数字三角形的层数。 接下来 $n$ 行,每行包含若干整数,其中第 $i$ 行表示数字三角形第 $i$ 层包含的整数。 **输出格式** 输出一个整数,表示最大的路径数字和。 **数据范围** $1≤n≤500$,$−10000≤$三角形中的整数$≤10000$ **输入样例:** ```cpp {.line-numbers} 5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 ``` **输出样例:** ```cpp {.line-numbers} 30 ``` ### 二、$dfs$ ```cpp {.line-numbers} #include using namespace std; const int N = 510; int a[N][N]; int n; /** 深度优先搜索关键点: 1、递归出口 本题是超出n行,来到了第n+1行,就意味着搜索结束,可以统计答案了 2、每一个位置,面临两种选择,分别是正下方和右下方,需要计算它们每一条路线的总和, sum(left,right,a[x][y]) 3、需要使用int返回,因为从每个坐标出发,最终都需要返回一个sum和,而这个sum对于调用者 是有用的,不用采用全局变量,只能用返回int形式。 4、向下传递,用参数;向上传递,用返回值 */ int dfs(int x, int y) { if (x == n + 1) return 0; // 你们不要指望我了,我是0,你们该咋办就咋办吧~ if (y > x) return 0; return max(dfs(x + 1, y), dfs(x + 1, y + 1)) + a[x][y]; } int main() { cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> a[i][j]; int res = dfs(1, 1); printf("%d", res); return 0; } ``` 结果:简单样例通过 本题数据范围 $1≤n≤500, −10000≤$三角形中的整数$≤10000$ 因为爆搜的时间复杂度是 $2^{n-1}$,所以$500$的话就是$2^{499}$,肯定是$TLE$了!需要想办法优化。 ### 三、记忆化搜索 ```cpp {.line-numbers} #include using namespace std; const int N = 510; int a[N][N]; int f[N][N]; // 记录以(x,y)坐标位置为根的子树的路径最大和 int n; int dfs(int x, int y) { if (f[x][y]) return f[x][y]; // 如果算过,就直接返回 if (x == n + 1) return 0; // 你们不要指望我了,我是0,你们该咋办就咋办吧~ if (y > x) return 0; // 算过的都记录下来,别丢掉,左下方,x+1,y,右下方,x+1,y+1 f[x + 1][y] = dfs(x + 1, y); f[x + 1][y + 1] = dfs(x + 1, y + 1); f[x][y] = max(f[x + 1][y], f[x + 1][y + 1]) + a[x][y]; return f[x][y]; } int main() { cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> a[i][j]; int res = dfs(1, 1); printf("%d", res); return 0; } ``` ### 四、$DP$ 动态规划 [自底向上+二维数组] * 状态表示 $f[i][j]$ 集合:**从底向上** 走到$(i,j)$所有路线的集合 属性:$max$ * 状态计算 $f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j]$ ```cpp {.line-numbers} #include using namespace std; const int N = 1e4 + 10; int n, a[N][N]; int f[N][N]; int main() { cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> a[i][j]; for (int i = n; i >= 1; i--) // 从下向上去递推 for (int j = 1; j <= i; j++) f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j]; printf("%d\n", f[1][1]); return 0; } ``` ### 五、$DP$ 动态规划 [自底向上+一维数组] ```cpp {.line-numbers} #include using namespace std; const int N = 1e4 + 10; int f[N]; int a[N][N]; int main() { int n; cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> a[i][j]; for (int i = n; i >= 1; i--) for (int j = 1; j <= i; j++) f[j] = max(f[j + 1], f[j]) + a[i][j]; printf("%d", f[1]); return 0; } ``` ### 六、$DP$ 动态规划 [自上向下+二维数组] ```cpp {.line-numbers} #include using namespace std; const int N = 510; const int INF = 0x3f3f3f3f; int f[N][N]; int a[N][N]; int main() { int n; cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> a[i][j]; memset(f, -0x3f, sizeof f); // 预求最大,先设最小 // 其实自上而下版本之所以需要初始化-INF,是因为每个枚举到的位置需要从它头顶和它的左上侧取值对比 // 而它的正上方可能是不存在的,如果不初始化,就是0 // 本题数据还可能是负数,就会造成它可会从正上方取来一个0做为最大值,造成结果错误 f[1][1] = a[1][1]; for (int i = 2; i <= n; i++) for (int j = 1; j <= i; j++) f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + a[i][j]; int res = -INF; for (int i = 1; i <= n; i++) res = max(res, f[n][i]); printf("%d\n", res); return 0; } ``` ### 七、$DP$ 动态规划 [自上向下+一维数组] ```cpp {.line-numbers} #include using namespace std; const int N = 510; const int INF = 0x3f3f3f3f; int n; int f[N]; int a[N][N]; int main() { cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> a[i][j]; // 预求最大,先设最小 memset(f, -0x3f, sizeof f); f[1] = a[1][1]; // 递推的初始化边界 for (int i = 2; i <= n; i++) for (int j = i; j >= 1; j--) f[j] = max(f[j - 1], f[j]) + a[i][j]; int res = -INF; for (int i = 1; i <= n; i++) res = max(res, f[i]); printf("%d", res); return 0; } ```