石子合并 (区间DP)
描述
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值,占单独的一行
样例输入
3
1 2 3
样例输出
9
首先从集合的角度考虑状态,用 f [i, j] 应该表示的是怎样一个状态,因为最后要合并的是全部的石子,合并石子的方案有很多,我们可以用 f [i, j] 表示第 i 到 j 区间内的石子合并的全部方案,用 f [i, j] 的值表示所有方案中的最小代价,因此初始化时应该把 f 数组初始化为极大值,考虑状态转移,f [i, j] 一定可以由 i 到 j 中间一个点分开,分成两堆,再次合并,因此假设从 k 这个点分成两半,即有 f [i, j] = min(f [i, k - 1] + f[k, j] + 两堆石子合并的代价),两堆石子合并的代价就是区间 i 到 j 内的和,可以用前缀和的方式快速求解,方程虽然这样写,但是并不能以直接枚举区间的起点和终点,因为我们要保证在计算当前状态时,所需要的状态都被计算过了,可以枚举区间长度,从小到大,在计算大长度区间时所需要的小长度一定被计算过了,然后枚举起点,最后枚举中间的断点 k 即可
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 310;
int a[N], f[N][N], sum[N];
int n;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)scanf("%d", &a[i]);
for (int i = 1; i <= n; i++)sum[i] = sum[i - 1] + a[i];
memset(f, 0x3f, sizeof f);
for (int len = 1; len <= n; len++){
for (int l = 1; l + len - 1 <= n; l++){
int r = l + len - 1;
for (int k = l; k <= r; k++){
if(k - 1 >= l)f[l][r] = min(f[l][r], f[l][k - 1] + f[k][r] + sum[r] - sum[l - 1]);
if(l == r)f[l][r] = 0;
}
}
}
printf("%d\n", f[1][n]);
return 0;
}
本文来自博客园,作者:correct,转载请注明原文链接:https://www.cnblogs.com/correct/p/12862058.html

浙公网安备 33010602011771号