1. 程式人生 > >【51Nod 1021 】石子歸併 【區間DP】

【51Nod 1021 】石子歸併 【區間DP】

N堆石子擺成一個環。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的代價。計算將N堆石子合併成一堆的最小代價。

例如: 1 2 3 4,有不少合併方法
1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)
1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)
1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)

括號裡面為總代價可以看出,第一種方法的代價最低,現在給出n堆石子的數量,計算最小合併代價。
Input
第1行:N(2 <= N <= 1000)
第2 - N + 1:N堆石子的數量(1 <= A[i] <= 10000)
Output
輸出最小合併代價
Input示例
4
1
2
3
4
Output示例
19

分析: dp[i][j] 表示 區間[i, j] 進行合併的最小价值,然後我們會發現,大區間的劃分可以有好多種,而且每種都是可以用小區間來表示的,所以我們可以DP;
關鍵: 用小區間來遞推大區間。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;

const int N = (int) 100 + 11;
const int M = (int) 10000 + 11 ;
const int MOD = (int) 1e9 + 7 ;
const
ll inf = 0x3f3f3f3f3f3f3f3f; int val[N]; int dp[N][N], sum[N][N]; int main(){ int n; scanf("%d", &n); for(int i = 1; i <= n; i++){ scanf("%d", &val[i]); } for(int i = 1; i <= n; i++){ for(int j = i; j <= n; j++){ sum[i][j] = sum[i][j - 1
] + val[j]; } } memset(dp, 0x3f, sizeof(dp) ); for(int i = 0; i <= n; i++) dp[i][i] = 0; for(int len = 2; len <= n; len++){ // 列舉區間長度len for(int j = 1; j + len - 1 <= n; j++){ // 列舉所有長度為len的區間[L, R] int L = j, R = L + len - 1; for(int k = L; k < R; k++){ // 對於每個區間都可以劃分為len - 1個方案數 dp[L][R] = min(dp[L][R], dp[L][k] + dp[k + 1][R] + sum[L][R]); } } } printf("%d\n", dp[1][n]); return 0; }