题解:AcWing 4920 乘法谜题
【题目来源】
AcWing:4920. 乘法谜题 - AcWing题库
【题目描述】
乘法谜题是一个益智游戏,规则如下:
- 给定一个长度为 \(n\) 的正整数序列。
- 在每轮操作中,你需要选择当前序列中的一个现存元素(不能选第一个元素和最后一个元素)并将其从序列中消除。在消除该元素前,其左右两侧一定都存在相邻元素(相邻元素指现存元素中与其相邻的元素),将这三个元素相乘得到的乘积就是你此轮操作的得分。
- 当序列长度不足 \(3\) 时,游戏结束。
我们的目标是最小化所有操作的总得分。
例如,给定序列 \(10,1,50,20,5\),如果我们依次选择消除 \(1,20,50\),则总得分为 \(10×1×50+50×20×5+10×50×5=500+5000+2500=8000\),如果我们依次选择消除 \(50,20,1\),则总得分为 \(1×50×20+1×20×5+10×1×5=1000+100+50=1150\)。
【输入】
第一行包含整数 \(N\)。
第二行包含 \(N\) 个整数,表示给定正整数序列。
【输出】
一个整数,表示总得分的最小可能值。
【输入样例】
6
10 1 50 50 20 5
【输出样例】
3650
【核心思想】
-
问题分析:给定一个长度为 \(n\) 的正整数序列,每次操作选择一个非首尾元素删除,得分等于该元素与其左右相邻元素的乘积。求所有操作总得分的最小值。这是一个区间DP问题,关键在于找到最优的删除顺序使得总得分最小。
-
算法选择:
- 区间DP:定义
f[i][j]为消除区间 \((i, j)\) 内所有元素(保留 \(i\) 和 \(j\) 作为边界)的最小得分 - 最优子结构:大区间最优解由小区间最优解组合而成
- 枚举分割点:对于区间 \([i, j]\),枚举最后删除的元素 \(k\),将问题分解为两个子问题
- 区间DP:定义
-
关键步骤:
- 状态定义:
f[i][j]表示保留端点 \(i\) 和 \(j\),消除区间 \((i, j)\) 内所有元素的最小得分 - 初始化:
f[i][i] = 0,f[i][i+1] = 0(相邻元素之间无需消除) - 状态转移(枚举区间长度 \(len\) 从 \(3\) 到 \(n\)):
- 枚举左端点 \(i\),计算右端点 \(j = i + len - 1\)
- 初始化
f[i][j] = INF - 枚举中间点 \(k\)(\(i < k < j\)):
f[i][j] = min(f[i][j], f[i][k] + f[k][j] + a[i] * a[k] * a[j])- 解释:先消除 \((i, k)\) 和 \((k, j)\) 区间,最后删除 \(k\),得分为 \(a[i] \cdot a[k] \cdot a[j]\)
- 结果:
f[1][n]即为整个序列的最小总得分
- 状态定义:
-
时间/空间复杂度:
- 时间复杂度:\(O(n^3)\),三层循环:区间长度、左端点、分割点
- 空间复杂度:\(O(n^2)\),二维DP数组
-
区间DP的核心思想:
- 区间定义:
f[i][j]表示子问题区间 \([i, j]\) 的最优解 - 长度递增:按区间长度从小到大计算,保证计算大区间时小区间已求解
- 枚举分割点:通过枚举中间点 \(k\) 将大区间分解为两个小区间
- 最优子结构:大区间的最优解由小区间的最优解组合而成
- 删除顺序的转化:通过枚举最后删除的元素,将问题分解为左右两个子问题
- 适用于区间合并、矩阵链乘、最优删除顺序、石子合并类问题
- 区间定义:
【解题思路】


【算法标签】
区间DP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义数组最大容量
const int N = 105;
// 全局变量声明
int n; // 序列长度
int a[N]; // 存储原始正整数序列
int f[N][N]; // 动态规划数组,f[i][j]表示子序列a[i..j]的最小总得分
// 主函数入口
int main()
{
// 读取序列长度
cin >> n;
// 读取序列中各元素的值
for (int i = 1; i <= n; i++)
cin >> a[i];
// 初始化dp数组为无穷大
memset(f, 0x3f, sizeof(f));
// 边界条件:
// - 长度为1的区间:只有一个元素,无法进行任何消除操作,得分为0
// - 长度为2的区间:只有两个元素,无法选择中间的"可消除元素",得分为0
for (int i = 1; i <= n; i++)
{
f[i][i] = 0; // 单元素区间,无可消除元素
f[i][i + 1] = 0; // 双元素区间,首尾不可选,无可消除元素
}
// 区间DP:从小到大枚举区间长度
for (int len = 3; len <= n; len++)
{
// 枚举区间左端点
for (int i = 1; i + len - 1 <= n; i++)
{
int j = i + len - 1; // 区间右端点
// 枚举最后一个被消除的元素a[k](i < k < j)
// 消除a[k]时,其左右相邻元素为a[i]和a[j](因为k是区间内最后消除的)
// 得分 = a[i] * a[k] * a[j]
// 再加上左右两个子区间[i, k]和[k, j]的最小得分
for (int k = i + 1; k < j; k++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j] + a[i] * a[k] * a[j]);
}
}
// 输出整个序列的最小总得分
cout << f[1][n] << endl;
return 0;
}
【运行结果】
6
10 1 50 50 20 5
3650
浙公网安备 33010602011771号