1. 程式人生 > 實用技巧 >282. 石子合併

282. 石子合併

狀態轉移方程:
\(f(i, j) = min\{s[j] - s[l - 1] + f[i][k] + f[k + 1][j]\} (k = i, i + 1, ..., r - 1)\)

第一種(迴圈區間長度和左端點)

#include<iostream>
using namespace std;

const int N = 310, INF = 0x3f3f3f3f;
int f[N][N];
int a[N], s[N];
int n;

int main(){
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    for(int i = 1; i <= n; i ++) s[i] = a[i] + s[i - 1];
    
    /*
        1. 合併一堆石子的代價為0 <=> f[i][i] = 0
        2. len必須從2開始否則得到的矩陣會是[INF]nxn
        3. 使用列舉兩個區間端點的時候需要考慮保證之前的狀態被計算過
    */
    
    for(int len = 2; len <= n; len ++)
        for(int l = 1; l + len - 1 <= n; l ++){
            int r = l + len - 1;
            f[l][r] = INF;
            for(int k = l; k < r; k ++)
               f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
        }
        
    cout << f[1][n];
    
    return 0;
}

第二種(迴圈兩個區間端點)

#include<iostream>
using namespace std;

const int N = 310, INF = 0x3f3f3f3f;
int f[N][N];
int a[N], s[N];
int n;

int main(){
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    for(int i = 1; i <= n; i ++) s[i] = a[i] + s[i - 1];
    
    
    /*
        k + 1必定大於i,而此時f[k + 1][j]還沒有被計算,
        這就是為什麼迴圈i要倒著來的原因了。
    */
    
    for(int i = n; i >= 1; i --)
        for(int j = i + 1; j <= n; j ++){
            f[i][j] = INF;
            for(int k = i; k < j; k ++)
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
        }
        
    cout << f[1][n];
    
    return 0;
}