题解: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

【核心思想】

  1. 问题分析:给定一个长度为 \(n\) 的正整数序列,每次操作选择一个非首尾元素删除,得分等于该元素与其左右相邻元素的乘积。求所有操作总得分的最小值。这是一个区间DP问题,关键在于找到最优的删除顺序使得总得分最小。

  2. 算法选择

    • 区间DP:定义 f[i][j] 为消除区间 \((i, j)\) 内所有元素(保留 \(i\)\(j\) 作为边界)的最小得分
    • 最优子结构:大区间最优解由小区间最优解组合而成
    • 枚举分割点:对于区间 \([i, j]\),枚举最后删除的元素 \(k\),将问题分解为两个子问题
  3. 关键步骤

    • 状态定义f[i][j] 表示保留端点 \(i\)\(j\),消除区间 \((i, j)\) 内所有元素的最小得分
    • 初始化f[i][i] = 0f[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] 即为整个序列的最小总得分
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^3)\),三层循环:区间长度、左端点、分割点
    • 空间复杂度:\(O(n^2)\),二维DP数组
  5. 区间DP的核心思想

    • 区间定义f[i][j] 表示子问题区间 \([i, j]\) 的最优解
    • 长度递增:按区间长度从小到大计算,保证计算大区间时小区间已求解
    • 枚举分割点:通过枚举中间点 \(k\) 将大区间分解为两个小区间
    • 最优子结构:大区间的最优解由小区间的最优解组合而成
    • 删除顺序的转化:通过枚举最后删除的元素,将问题分解为左右两个子问题
    • 适用于区间合并、矩阵链乘、最优删除顺序、石子合并类问题

【解题思路】

img/Pasted image 20260612100331.png
img/Pasted image 20260612100819.png

【算法标签】

区间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
posted @ 2026-06-14 10:36  团爸讲算法  阅读(8)  评论(0)    收藏  举报