P1880-石子合并: 区间DP

416 阅读2分钟

题目: 石子合并

题目描述

在一个圆形操场的四周摆放 NN 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 NN 堆石子合并成 11 堆的最小得分和最大得分。

输入格式

数据的第 11 行是正整数 NN,表示有 NN 堆石子。

第 22 行有 NN 个整数,第 ii 个整数 aia_i 表示第 ii 堆石子的个数。

输出格式

输出共 22 行,第 11 行为最小得分,第 22 行为最大得分。

说明/提示

1N1000ai201\leq N\leq 100,0\leq a_i\leq 20

题解

状态表示: f(i,j)f(i, j) 表示合并 [i,j][i, j] 之间的石子所需要的最小花费

状态转移方程: f(i,j)=min{f(i,k)+f(k+1,j)+cost}f(i, j) = min\{f(i, k) + f(k + 1, j) + cost\}

对于 costcost 的值, 可以通过前缀和计算得出
且由于这些石子围成了一个环, 故可以将输入的数组扩大一倍, 变为 2×n2 \times n 堆, 其中第 ii 堆与第 i+ni+n 堆的值是一样的, 然后对这 2×n2 \times n 个堆进行计算, 其效果就和对环进行计算是一样的了

#include <cstring>
#include <iostream>

using namespace std;

const int N = 201;
int f[N][N], g[N][N], a[N];

int main() {
  int n;
  cin >> n;
  
  // 由于要用 f 来求最小值, 所以需要将 f 中的元素设置为极大值
  memset(f, 0x3f, sizeof(f));
  
  for (int i = 1; i <= n; ++ i) {
    cin >> a[i];
    a[i + n] = a[i];
  }
  
  // 开始的最晚位置至少得是 n, 对应扩展后数组的最大下标为 n * 2 - 1
  const int len = (n << 1) - 1;
  for (int i = 1; i <= len; ++ i) {
    a[i] += a[i - 1];
    f[i][i] = 0;
  }
  
  for (int i = 2; i <= n; ++ i) { // 合并 i 堆石子
    for (int l = 1; l + i - 1 <= len; ++ l) { // 从第 i 堆开始合并
      for (int k = l, r = l + i - 1; k < r; ++ k) { // 合并到第 r 堆
        f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + a[r] - a[l - 1]);
        g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + a[r] - a[l - 1]);
      }
    }
  }
  
  int minAns = 0x3f3f3f3f, maxAns = -1;
  for (int i = 1; i <= n; ++ i) {
    minAns = min(minAns, f[i][i + n - 1]);
    maxAns = max(maxAns, g[i][i + n - 1]);
  }
  cout << minAns << endl << maxAns;
  
  return 0;
}