矩陣鏈乘法:求最小運(yùn)算次數(shù) —— 動(dòng)態(tài)規(guī)劃經(jīng)典題

題目來(lái)源:算法競(jìng)賽 / 數(shù)據(jù)結(jié)構(gòu)與算法課程
難度:中等
核心考點(diǎn):動(dòng)態(tài)規(guī)劃、矩陣乘法結(jié)合律優(yōu)化


?? 題目描述

給定 n 個(gè)矩陣,第 i 個(gè)矩陣 M_i 的大小為 w_i × w_{i+1}。我們不關(guān)心矩陣內(nèi)容,只關(guān)心其維度。

我們需要將這些矩陣按順序相乘(稱為“鏈乘積”):

M = M_1 \times M_2 \times \cdots \times M_n

由于矩陣乘法滿足結(jié)合律不滿足交換律,我們可以選擇不同的括號(hào)化方式來(lái)減少總運(yùn)算次數(shù)。

?? 定義:一個(gè) a × b 的矩陣乘以一個(gè) b × c 的矩陣,需要 a × b × c 次標(biāo)量乘法。

目標(biāo):計(jì)算將這 n 個(gè)矩陣進(jìn)行鏈乘積所需的最少運(yùn)算次數(shù)


?? 輸入格式

  • 第一行:一個(gè)正整數(shù) n,表示矩陣數(shù)量。
  • 第二行:n + 1 個(gè)正整數(shù) w_0, w_1, ..., w_n,其中第 i 個(gè)矩陣 M_i 的維度是 w_i × w_{i+1}。

?? 輸出格式

輸出一個(gè)整數(shù),代表最小運(yùn)算次數(shù)。


?? 輸入輸出樣例

輸入 #1

3
5 3 2 6

輸出 #1

90

? 解析:

  • 矩陣1:5×3,矩陣2:3×2,矩陣3:2×6
  • 方案1:(M1 × M2) × M3 → (5×3×2) + (5×2×6) = 30 + 60 = 90
  • 方案2:M1 × (M2 × M3) → (3×2×6) + (5×3×6) = 36 + 90 = 126
  • 最小值為 90

?? 算法思路:動(dòng)態(tài)規(guī)劃(DP)

這是一個(gè)經(jīng)典的區(qū)間 DP 問(wèn)題。

?? 狀態(tài)定義

設(shè) dp[i][j] 表示將第 i 個(gè)到第 j 個(gè)矩陣連乘所需的最小運(yùn)算次數(shù)(1 ≤ i ≤ j ≤ n)。

?? 狀態(tài)轉(zhuǎn)移

考慮在區(qū)間 [i, j] 中枚舉分割點(diǎn) ki ≤ k < j),即:

  • 先計(jì)算 M_i × ... × M_k,得到一個(gè) w_i × w_{k+1} 的矩陣;
  • 再計(jì)算 M_{k+1} × ... × M_j,得到一個(gè) w_{k+1} × w_{j+1} 的矩陣;
  • 最后兩者相乘,代價(jià)為 w_i × w_{k+1} × w_{j+1}。

因此狀態(tài)轉(zhuǎn)移方程為:

dp[i][j] = \min_{i \leq k < j} \left( dp[i][k] + dp[k+1][j] + w_i \times w_{k+1} \times w_{j+1} \right)

?? 初始化

當(dāng) i == j 時(shí),只有一個(gè)矩陣,無(wú)需乘法:

dp[i][i] = 0

?? 最終答案

dp[1][n]


?? C++ 實(shí)現(xiàn)代碼

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {
    int n;
    cin >> n;

    vector<int> w(n + 1);
    for (int i = 0; i <= n; ++i) {
        cin >> w[i];
    }

    // dp[i][j] 表示從第 i 個(gè)矩陣到第 j 個(gè)矩陣連乘的最小代價(jià)
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));

    // 區(qū)間長(zhǎng)度 len 從 2 開(kāi)始(至少兩個(gè)矩陣才能相乘)
    for (int len = 2; len <= n; ++len) {           // len 是當(dāng)前考慮的矩陣個(gè)數(shù)
        for (int i = 1; i <= n - len + 1; ++i) {   // 起始位置 i
            int j = i + len - 1;                   // 結(jié)束位置 j
            dp[i][j] = INT_MAX;
            for (int k = i; k < j; ++k) {          // 枚舉分割點(diǎn)
                int cost = dp[i][k] + dp[k + 1][j] + w[i - 1] * w[k] * w[j];
                if (cost < dp[i][j]) {
                    dp[i][j] = cost;
                }
            }
        }
    }

    cout << dp[1][n] << endl;
    return 0;
}

? 注意:這里 w 數(shù)組下標(biāo)從 0 開(kāi)始,而矩陣編號(hào)從 1n,所以:

  • i 個(gè)矩陣維度是 w[i-1] × w[i]
  • 因此在狀態(tài)轉(zhuǎn)移中使用 w[i-1] * w[k] * w[j]

?? 時(shí)間復(fù)雜度分析

  • 狀態(tài)數(shù):O(n2)
  • 每個(gè)狀態(tài)轉(zhuǎn)移需枚舉 O(n) 個(gè)分割點(diǎn)
  • 總時(shí)間復(fù)雜度:O(n3)
  • 空間復(fù)雜度:O(n2)

對(duì)于 n ≤ 1000 的數(shù)據(jù)規(guī)模完全可接受。


?? 手動(dòng)模擬樣例(n=3, w=[5,3,2,6])

i\j 1 2 3
1 0 5×3×2=30 min{dp[1][1]+dp[2][3]+5×3×6, dp[1][2]+dp[3][3]+5×2×6} = min{0+36+90, 30+0+60} = 90
2 0 3×2×6=36
3 0

? 最終結(jié)果:dp[1][3] = 90


?? 總結(jié)

  • 矩陣鏈乘法是動(dòng)態(tài)規(guī)劃入門必學(xué)題,掌握它有助于理解區(qū)間 DP 和最優(yōu)子結(jié)構(gòu)。
  • 關(guān)鍵在于正確設(shè)置狀態(tài)和轉(zhuǎn)移方程。
  • 實(shí)際應(yīng)用中可用于優(yōu)化圖形渲染、機(jī)器學(xué)習(xí)中的張量運(yùn)算等場(chǎng)景。

?? 附錄:復(fù)制代碼按鈕(用于網(wǎng)頁(yè)文章)

如果你將本文發(fā)布在支持 HTML 的平臺(tái)(如掘金、CSDN),可添加以下代碼實(shí)現(xiàn)“一鍵復(fù)制”功能:

<button onclick="navigator.clipboard.writeText(document.getElementById('code').innerText)">?? 復(fù)制代碼</button>
<pre id="code">
// 上面的 C++ 代碼粘貼在這里
</pre>

?? 歡迎收藏、點(diǎn)贊、轉(zhuǎn)發(fā)!
如果你喜歡這類算法題解析,歡迎關(guān)注我,后續(xù)會(huì)持續(xù)更新「動(dòng)態(tài)規(guī)劃系列」、「圖論系列」、「字符串處理」等專題!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容