题目链接
石子合并
这样的定义,最后区间的合并一定是左边连续的一大堆和右边连续的一大堆进行合并。正因为这一点,我们才能用DP做。
有限集。
是有限的
一开始有n - 1种选法(一共有n堆),第二次是n - 2种选法(一共有n - 1堆)。。。。
状态表示:所有将[i, j]这一段合并成一堆的方案的集合。一共有(j - i + 1)堆,所以方案数是(j - i)!个。
如何状态分类?
以最后一次合并时候的分界点k来划分。
也就是左边的最后一堆是哪一堆来划分。
左边的最后一堆可以是第i堆,也就是左边只有1堆。
左边的最后一堆可以是第i + 1堆,也就是左边有2堆。
...
左边的最后一堆可以是第j - 1堆,因为起码得有两堆,所以这样的结果是右边这一堆只有1个,也就是第j堆。
我们来分析下其中的一个状态,选k个的时候,就是左边取最小,右边取最小,最后加上合并的代价。
枚举的顺序是先枚举区间长度len,len从2开始,因为len为1表示只有1堆,1堆是合并不了的,代价也是0,加上全局变量初值为0,所以len可以从2开始枚举。
枚举区间左端点
右端点是
因为第三重循环,是枚举不同k中的最小值,所以要将f[i][j]的初始值定义为2e9,不然状态转移的结果都是0,不能正确的进行状态转移。
f[1][n]表示所有将[1, n]合并成1堆的集合中的最小代价。
f[i][j]设置成2e9后,作用的是右下角那里,红色箭头指向。
代码实现
cpp代码
#include <iostream>
using namespace std;
const int N = 310;
int n;
int a[N], s[N];
int f[N][N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i];
f[0][0] = 0;
for(int len = 2; len <= n; len ++)
for(int i = 1; i + len - 1 <= n; i ++)
{
int j = i + len - 1;
f[i][j] = 2e9;
for(int k = i; k <= j - 1; k ++)
{
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
}
}
cout << f[1][n] << endl;
return 0;
}
核心:只能合并的是相邻两堆,如果没有这个限制,就变成了一个贪心的问题,比如合并果子。
并且是左边和右边连续的一部分