石子合并 区间DP模板题

题目链接:https://vjudge.net/problem/51Nod-1021

题意

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堆石子的数量,计算最小合并代价。

输入

第1行:N(2 <= N <= 100)
第2 - N + 1:N堆石子的数量(1 <= A[i] <= 10000)

输出

输出最小合并代价

样例输入

4
1
2
3
4

样例输出

19

题解:
这道题目如果其实我自己接触的时候还是很早的,我很早很早之前就在一场比赛当中接触到了这道题目,那是很久很久之前的一个春天,我大一的时候的一场校赛中,我唯一没有做出来TLE的题,因为当时还不知道记忆化搜索这个概念。
所以我当时的解法是用的dfs,没有使用记忆化进行剪枝。
这里的记忆化搜索其实可以对应动态规划的状态转移方程。
首先我们可以回到这道题目里面来,可以看到:
区间 [1..N] 经过 N-1 次合并会获得最终的一个数,那么在经过 N-2 次合并的时候是会还剩2个数的,而且这两个数肯定还是连续的 a[i]a[i+1] ,所以,如果我们设 dp[i][j] 表示合并区间 [i,j] 的最小代价,那么:

dp[i][j] = min(dp[i][k] + dp[k+1][j]) + sum(i,j) , 其中k=i,i+1,...,j-1

这里的 sum(i,j) 表示 a[i]+a[i+1]+...+a[j]

那么 dp[i][j] 怎么求呢,我们可以通过定义一个函数 dfs(i,j) 来求得,但是可以通过记忆化搜索进行剪枝优化,其实就是写DP的时候记录的一些中间结果对应的重叠子问题。

代码如下(这个代码我没有验证过正确性,不过应该是没有问题的,主要理解他的思想):

#include <iostream>
using namespace std;

const int maxn = 1010;

int n, a[maxn], dp[maxn][maxn], sum[maxn];

int dfs(int l, int r) {
    if (l == r) return 0;
    if (l+1 == r) {
        return dp[l][r] = a[l] + a[r];
    }
    if (dp[l][r])
        return dp[l][r];
    for (int i = l; i < r; i ++) {
        int tmp = dfs(l,i) + dfs(i+1, r) + sum[r] - sum[l-1];
        if (!dp[l][r]) dp[l][r] = tmp;
        else dp[l][r] = min(dp[l][r], tmp);
    }
    return dp[l][r];
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        sum[i] = sum[i-1] + a[i];
    }
    cout << dfs(1, n) << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/zifeiy/p/10716703.html