题目解析
这道题是一道非常经典的区间动态规划 (Interval DP) 题目。
和 石子合并 这个题目的思路是一样的,从后(时间)往前思想
1. 核心思维:逆向思考(最后一张是谁?)
如果顺着想“第一步取哪张卡片”,你会发现之后的局面变得很复杂,因为相邻关系变了。
技巧:我们要倒着想。
假设我们只关注一段区间 [i, j](即从第
这一段区间最终只会剩下两张卡片:边界
那么,在这一段区间里,肯定有一张卡片
当我们要取走这最后一张卡片
此时取走
2. 动态规划定义
我们定义
将第
- 目标:求
。 - 初始条件:
- 当
时(例如第1张和第2张),中间没有卡片可以取,所以 。
- 当
3. 状态转移方程
对于区间
总代价由三部分组成:
- 先把
到 之间的取光的最小代价: - 先把
到 之间的取光的最小代价: - 最后取走
的代价:
公式如下:
4. 算法流程 (区间 DP 模板)
区间 DP 的遍历顺序很讲究,通常遵循以下三层循环:
- 枚举区间长度 (
len):先解决小区间(长度为2, 3…),再解决大区间。 - 枚举起点 (
i):确定区间的左端点,算出右端点。 - 枚举分割点 (
k):尝试每一个可能的最后一张卡片。
5. 图解演示
假设输入为:10 1 50 20 (
目标是求
第一轮:长度 len=3 (中间隔1个数)
- 区间 [1, 3] (即 10, 1, 50):
- 中间只能取
(也就是数字1)。 - 代价 =
。 。
- 中间只能取
- 区间 [2, 4] (即 1, 50, 20):
- 中间只能取
(也就是数字50)。 - 代价 =
。 。
- 中间只能取
第二轮:长度 len=4 (中间隔2个数)
- 区间 [1, 4] (即 10, 1, 50, 20):
- 选择 A:最后取
(数字1) - 意味着先搞定右边的 [2, 4] (变成 1和20),再取1。
- 代价 =
。
- 选择 B:最后取
(数字50) - 意味着先搞定左边的 [1, 3] (变成 10和50),再取50。
- 代价 =
。
- 比较:1200 < 10500,所以最优解是 1200。
- 选择 A:最后取
6. C++ 代码实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits> // for INT_MAX
using namespace std;
const int MAXN = 105;
int arr[MAXN];
int dp[MAXN][MAXN];
int main() {
// 优化IO
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N;
if (!(cin >> N)) return 0;
for (int i = 1; i <= N; ++i) {
cin >> arr[i];
}
// 初始化 DP 数组
// dp[i][i+1] 默认为 0 (相邻两张卡没法取中间的)
// 其他初始化为一个很大的数,方便求 min
for (int i = 1; i <= N; ++i) {
for (int j = 1; j <= N; ++j) {
dp[i][j] = 0;
}
}
// 核心代码:区间 DP
// 1. 枚举区间长度 len。
// 最小区间包含3张卡片(左、中、右),长度定义为右下标-左下标
// len = 2 表示 i 和 i+2 之间有一个数
for (int len = 2; len < N; ++len) {
// 2. 枚举左端点 i
for (int i = 1; i <= N - len; ++i) {
int j = i + len; // 计算右端点 j
dp[i][j] = INT_MAX; // 先设为无穷大
// 3. 枚举中间的分隔点 k (即最后被取走的卡片)
// k 必须在 i 和 j 之间
for (int k = i + 1; k < j; ++k) {
int cost = dp[i][k] + dp[k][j] + arr[i] * arr[k] * arr[j];
if (cost < dp[i][j]) {
dp[i][j] = cost;
}
}
}
}
// 输出从 1 到 N 全部取完的最小代价
cout << dp[1][N] << endl;
return 0;
}
总结与提示
- 复杂度:这实际上是一个
的算法。因为 ,运算次数大约是 ,非常安全。 - 易错点:
- 循环顺序:一定要先枚举长度
len,再枚举起点i。如果直接双层循环i和j,会导致计算大区间时,小区间的值还没算出来。 - 边界:注意
k的范围是i+1到j-1,不能等于i或j,因为首尾卡片不能取。
- 循环顺序:一定要先枚举长度