ACwing 282 - 石子合并(区间DP)

设有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

题目大意:

输入一个n,接下来输入 n 个数,ai 表示合并第 i 个石子的质量,你需要将这些石子合并成一堆,每次只能选择相邻的两堆进行合并,合并的代价是两堆石子质量之和,你需要找出一种方式使得合并完的代价最小,并输出这个代价。

解题思路:

经典区间dp问题,从两方面分析这个问题:

  • 状态表示: 用两维去表示状态,区间问题一般都可以这样表示,f(i, j) 表示从第 i 堆石子合并到第 j 堆石子所需代价,而这道题的属性是最小值,所以用f(i, j) 表示从第 i 堆合并到第 j 堆的最小代价。
  • 状态计算: 看看如何划分这个集合,f[i][j] 可以从什么状态转移过来呢,第一重循环可以枚举长度len,从1 -> n 第二重枚举一个起点,i到i + len - 1 ,考虑最后一次合并,他一定是将两堆石子合并成一堆,所以第三重循环可以枚举分割点k ,所以每次合并的代价一定是max(f[l][r], f[l][k] + f[k + 1][r] + (sum[r] - sum[l - 1])) sum为前缀和数组,因为这次合并的代价要加上l - r 的总和,维护一个前缀和可以O1 时间去得到sum[l, r], k 的范围是i -> j - 1 ,因为至少要有一堆,以k为分界点,最后输出f(1, n) 即可。

Code:

#include <iostream>
#include <cstring>

using namespace std;

const int N = 310;

int n;
int s[N]; 
int f[N][N];

int main()
{
    cin >> n;
    
    for (int i = 1; i <= n; i ++)
    {
        int x;
        cin >> x;
        s[i] = s[i - 1] + x;//维护一个前缀和
    }
    
    for (int len = 2; len <= n; len ++)//长度是1的代价一定是0,所以不必从1开始枚举
        for (int i = 1; i + len - 1 <= n; i ++)
        {
            int l = i, r = i + len - 1;
            
            f[l][r] = 0x3f3f3f3f;//起初这堆的代价是不知道的,初始化成一个非常大的值即可
            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] << endl;
    
    return 0;
}
posted @ 2020-09-14 20:58  Hayasaka  阅读(73)  评论(0编辑  收藏  举报