石子合并 (区间DP) (2023-02-09)

题目链接: https://vjudge.net/contest/537778#problem/F

题意

给出 n 堆石子,每堆有 {ai} 个石子,每次合并相邻的两堆石子可以获得这两堆石子的数量的得分。

求能够获得的得分的最大值和最小值。

1≤n≤100,0≤ai≤20。

思路

区间dp。设 dp[l][r] 为在区间 [l, r] 上能获得的最值,那么有

dp[l][r] = min/max({dp[l][k]+dp[k+1][r]}),其中 k 属于 [l, r]

这题中由于所有石子围成一圈,所以头和尾是可以合并的。这样的话需要把整个数组复制一遍,然后枚举一下不同的数列排序,比如 [1,2,3]->[2,3,1]->[3,1,2]

用一遍记忆化搜索就可以了。

时间复杂度:O(n^3)

代码

#include <iostream>
#include <queue>
#include <climits>

#define orep(i,l,r) for(int i = (l); i < (r); ++i)
#define NL          '\n'

const int maxN = 2e2+5;
int arr[maxN];
long long mi[maxN][maxN], mx[maxN][maxN];

long long dfsMx(const int l, const int r) {
  if (mx[l][r]) { return mx[l][r]; }
  if (l == r) { return 0; }
  long long max = 0;
  orep(i, 0, r-l) {
    max = std::max(dfsMx(l, l+i) + dfsMx(l+i+1, r), max);
  }
  return mx[l][r] = max + arr[r]-arr[l-1];
}

long long dfsMi(const int l, const int r) {
  if (mi[l][r]) { return mi[l][r]; }
  if (l == r) { return 0; }
  long long min = 1ll<<62;
  orep(i, 0, r-l) {
    min = std::min(dfsMi(l, l+i) + dfsMi(l+i+1, r), min);
  }
  return mi[l][r] = min + arr[r]-arr[l-1];
}

int main() {
  int n; std::cin >> n;
  orep(i, 1, n+1) { std::cin >> arr[i]; arr[i+n] = arr[i]; arr[i] += arr[i-1]; }
  long long r1=1ll<<62, r2=0;
  orep(i, 1, n+1) {
    arr[i+n] += arr[i+n-1];
    r1 = std::min(dfsMi(i, i+n-1), r1);
    r2 = std::max(dfsMx(i, i+n-1), r2);
  }

  std::cout << r1 << NL;
  std::cout << r2 << NL;

  return 0;
}