石子合并(区间dp)题解
[原题链接](P1880 [NOI1995] 石子合并 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
题目描述:
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。
哇!圆形,我链形都没搞明白也太难为我了吧┭┮﹏┭┮。
所以我们先把链形的石子合并搞明白,当然如果已经搞懂了可以直接跳过哈。
[例题链接](P1775 石子合并(弱化版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
题目大意:
有 n 堆石子于一排放置,问我们要将所以石子合在一起需要最少需要多少代价。
代价:合并两堆石子的质量之和;
由此可知,我们将 [ a, b ] 和 [ b + 1, c ] 这两个区间合并在一起时,需要付出的代价是这俩个区间的和,但是我们该如何去求这个区间和呢?没错,我们一次一次跑循环就好了。:(
开玩笑,当然不是跑循环,我们需要用到前缀和,用一个数组 s[ i ] 来表示从第一个数到第 i 个数的和,如果我们需要求一个区间的和 [ i , j ] ,我们只需要 s[ j ] - s[ i - 1 ] 就OK辣。:)
**如果减的是 s[ i ] 那么第 i 个数就算漏了。
现在来分析一下石子合并的最优解,我们不知道我们需要去合并哪些区间,所以这里我们的 f ( i, j ) 表示的是一个区间,也就是第 i 个数到第 j 个数的区间。
**因为题意要求求最小代价,所以我们需要将 f [ ] [ ] 初始化为一个很大很大的值,我比较喜欢用 0x3f3f3f3f 来表示最大值
**在这里还需要初始化 f [ i ] [ i ] 初始化其值为 0 ,因为一个堆没其他的堆找他合并
我们需要将一个个小区间的最优解一步一步合成整个大区间的最优解,所以在线性DP的基础上我们还需要在外卖包一层循环来表示区间的长度
如果我们要找到一个区间 [ i , j ] 的最优解我们则需求去在这个区间中找到一个 k ,通过 k 值将一个区间分为两个区间然后求这两个区间的解;
所以我们可以通过枚举 k 的形式来求一个区间的最优解;因为是在同一个集合里面找最优解那么这里的 [ i , j ] 是不会改变的所以 s[ j ] - s[ i - 1 ] 也不会改变。
1 for(int len = 2; len <= n; len++)//len表示长度 2 { 3 for(int i = 1; i + len - 1 <= n; i++) 4 { 5 int j = i + len - 1;// 知道了长度就可以求出区间 [i][j] 6 for(int k = i; k < j; k++)
// 用 k 去把区间[i][j]分成两个区间,然后求这两个区间合并的最优解
// 通过枚举 k 的方式求区间[i, j]中各种合并策略的所需的最优方法 7 { 8 f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]); 9 } 10 } 11 }
因为这个需要我们输出的是所有堆的最小代价,也就是区间 [ 1 , n ] 的值,所以这里我们直接输出 f [ 1 ] [ n ] 就OK了。
**上面的很重要!!没看懂再看看,实在不懂来骂我。
等等,没完!!!我们要写的是圆形石子合并。
因为我们已经理解了链形的石子合并所以我们现在只需要将圆形转换成链式就OK了。
来举个栗子:

我们将这个环沿着 a 点剪开,让它变成一条链,这条链的头和尾都 a 。我们可以这样去将这个圆形去变成一条链;
不过不同的是每一个点都可以成为我们剪开的点所以我们不妨在结尾加上每一个我们剪切的点:
比如一个圆上的数依次为:1 2 3 4,其中 1 和 4 相连,我们把这个圆变成: 1 2 3 4 1 2 3 4;
第一个 1 到 第二个 1 就像是我们将一个圆从点 1 剪开,2, 3, 4 以此类推。
那此时的操作就是将原本存放圆上面数的数组 a[ 1 ~ n ] 延长成 a [ 1 ~ n 1 ~ n ]也成了 a [ 1 ~ 2n ];
之后的操作就和前面链形的操作很像了。这里要注意我们的区间长度一定不能大于 n ,不然就越界了。
然后我们所算出来的 f [ i ] [ i + n - 1 ] 表示的是以第 i 个数为起点长度为 n 的链的最优解,那这么我们还需要跑一个循环去对比以 1 ~ n 的每一个点为起点的最优值来取最后的答案。
OK结束,很难🐎,QAQ。雀实有点(*^_^*)。
完整代码如下:
之后再发。

浙公网安备 33010602011771号