石子合併(區間DP模板)

2021-10-22 18:49:31 字數 2699 閱讀 3087

設有n堆石子排成一排,其編號為1,2,3,…,n。

每堆石子有一定的質量,可以用乙個整數來描述,現在要將這n堆石子合併成為一堆。

每次只能合併相鄰的兩堆,合併的代價為這兩堆石子的質量之和,合併後與這兩堆石子相鄰的石子將和新堆相鄰,合併時由於選擇的順序不同,合併的總代價也不相同。

例如有4堆石子分別為 1 3 5 2, 我們可以先合併1、2堆,代價為4,得到4 5 2, 又合併 1,2堆,代價為9,得到9 2 ,再合併得到11,總代價為4+9+11=24;

如果第二步是先合併2,3堆,則代價為7,得到4 7,最後一次合併代價為11,總代價為4+7+11=22。

問題是:找出一種合理的方法,使總的代價最小,輸出最小代價。

第一行輸入乙個數n表示石子的堆數n。

第二行輸入n個數,表示每堆石子的質量(均不超過1000)。

輸出乙個整數,表示最小代價。

1≤n≤300

4

1 3 5 2

22
石子合併問題是經典的區間dp問題。對於石子合併問題,不同的合併順序產生的代價也是不同的,這與矩陣連乘問題類似。與普通的哈夫曼樹問題不同的是,哈夫曼樹可以合併任意兩堆,而石子合併問題只能合併相鄰兩堆。

狀態表示:f[i][j]表示從第i堆到第j堆石子合併的最小代價。合併的方式有很多種,必然是先合併某個位置相鄰的兩堆,...,最後還剩下兩堆,將它們合併為一堆。一般以最後一次操作的方式來進行狀態劃分,即最後一次合併的是f[i][k]和f[k+1][j],也就是說先分別合併第i到k堆的石子和第k+1到j堆的石子,最後再合併剩下的兩堆石子。合併的總代價是f[i][j] = f[i][k] + f[k+1][j] + sum(i,j),k從i到j-1。簡單的解釋下就是,合併的總代價等於合併i到k的代價加上合併k+1到j的代價加上第i到j堆石子的總質量。區間內石子的總質量可以用字首和預處理出來,s[i] = h[1] + ... + h[i],則sum(i,j) = s[j] - s[i-1]。狀態轉移方程為f[i][j] = min(f[i][k] + f[k+1][j] + sum(i,j)),k從i到j-1。邊界情況是區間長度為1,即不進行合併,代價為0,對於區間長度大於1的區間,需要先將f[i][j]預處理為乙個較大的數,以便於求最小代價。

注意本題狀態轉移的方向,從區間長度小的向區間長度大的逐步擴散,故外層迴圈應該表示區間長度len,第二層迴圈表示區間起點i,區間終點j就等於i + len - 1,最後一層迴圈用來列舉最後一次進行合併從而產生乙個小區間的位置k。石子合併問題可以用四邊形法則進行優化,這裡暫不進行介紹。

核心:最後一次合併一定是左邊連續的一部分和右邊連續的一部分進行合併

狀態表示:f[i][j] 表示將 i 到 j 合併成一堆的方案的集合,屬性 min

狀態計算:

問題答案: f[1][n]

所有的區間dp問題,第一維都是列舉區間長度,一般 len = 1 用來初始化,列舉從 len = 2 開始,第二維列舉起點 i (右端點 j 自動獲得,j = i + len - 1)

for (int i = 1; i <= n; i++) 

for (int len = 2; len <= n; len++) //區間長度

for (int i = 1; i + len - 1 <= n; i++)

}

#include#includeusing namespace std;

#define n 310

#define inf 1e9 //很大的數,用於min()

int f[n][n] = ; //邊界情況:區間長度為1,即不進行合併,代價為0 ,故需將其初始化為0

int h[n], s[n] = ;

int main()

/* 區間 dp 列舉套路:長度+左端點 */

//先列舉區間長度

//因為合併石堆至少需要兩堆,所以區間長度從2開始

for(int len=2; len<=n; len++)

for (int i = n; i >= 1; i--)

f[i][j] = 1e9;

for (int k = i; k < j; k++) }}

cout << f[1][n] << endl;

return 0;

}

如果學過記憶化搜尋,那也可以用下面的**。雖然時間會比遞推稍微慢一丟丟,但是他的思路比較好寫。

#include#include#includeusing namespace std;

const int n = 307;

int a[n], s[n];

int f[n][n];

// 記憶化搜尋:dp的記憶化遞迴實現

int dp(int i, int j)

int main()

memset(f, -1, sizeof f);

cout << dp(1, n) << endl;

return 0;

}

石子合併 (區間DP

問題描述 在乙個操場上擺放著一行共n堆的石子。現要將石子有序地合併成一堆。規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆石子數記為該次合併的得分。請編輯計算出將n堆石子合併成一堆的最小得分和將n堆石子合併成一堆的最大得分。輸入檔案 輸入第一行為n n 1000 表示有n堆石子,第二行為n個用空...

石子合併 區間dp

有n堆石子排成一排,每堆石子有一定的數量。現要將n堆石子並成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過n 1次合併後成為一堆。求出總的代價最小值。假設dp 1 4 表示將區間1 4的石子合併所花費的代價。dp 1 4 可以劃分為dp 1 1 dp 2...

石子合併 (區間DP)

題目鏈結 描述 有n堆石子排成一排,每堆石子有一定的數量。現要將n堆石子並成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過n 1次合併後成為一堆。求出總的代價最小值。輸入第一行有乙個整數n,表示有n堆石子。接下來的一行有n 0 n 200 個數,分別表示...