[每日一题]石子合并 -- 区间DP
饭前点心:
区间Dp我也不会,哈哈,正好一起学习一下,做一下这道区间DP入门题。
摘要:
区间DP其实也是线性dp的一种,只是由于其实在太规律,所以分成一类以方便学习!!
要点:状态必然包含区间是哪个[i,j],通过枚举区间分界点进行转移。 也就是说一个大区间是由两个子区间合并来的或者是两个子区间加上中间元素合并来的!在合并 的时候自然是要满足最优化原理和无后效性原则…… 不能多说,到题目中去体会! 这类问题经常会遇到环,环的处理办法通常有两种: 謱)加倍——将数据复制加倍,就可以保证最后一个与第一个相连; 謲)取余——在调用数组时对n取余。 有可能需要提前处理合并区间的费用,如何处理视情况而定,不要忘记预处理和前缀和的办法! 经典例题有:石子合并,数链剖分,括号匹配,田忌赛马謬 凸多边形三角划分等
以上内容均摘要 dsy -- 雨神 大佬
侃侃:
区间 DP 嘛,我们都知道 dp 是由多个子问题最终组合成一个大的问题,并且满足无后效性等多种特性(菜鸡的浅显理解)
那么对于区间DP 来说,道理依然如此,即分成若干个子区间,子区间组成一个大的区间最后形成一个总区间。
题目:
分析:
(由于自己DP太菜,目前水平还尚未达到,即使基础的题目来说,也需要先参考一下别人写的,如有写的不到位的
地方,望各位路过的大佬能够不吝赐教)
对于这道题,先看一下数据范围,10^2,那么我们完全可以走双重循环甚至三重循环的线路。
当然也可以尝试着用 DFS 搞一搞。
我们发现(实际上是大佬们发现),大的方面来说,每一堆石子都是由两堆石子组成的,
也就是说 L ~~ R 这一堆石子是由 L ~ k + (k + 1) ~ R 这两堆石子组成的,但是中间
的 k 可能有很多堆,我们不知道 L 和 R 将 哪个 k 作为中间的分界点然后可以使得总代
价最小,所以我们需要去枚举所有可能的情况。
我们可以从小到大枚举所有区间,从而做到不重不漏(与线段树类似)
具体看代码吧.
Code:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 305;
int dp[maxn][maxn];
int a[maxn],sum[maxn];
int n;
/*
dp[i][j]: 把最初的第 i 堆 ~ 第 j 堆石子合并为一堆,所花费的最小代价
(实际上就是一个集合,从小到大枚举长度实际上相当于一堆一堆
合并)
*/
int main(void) {
scanf("%d",&n);
for(int i = 1; i <= n; i ++) {
scanf("%d",&a[i]);
// 求取前缀和,便于我们计算将两堆合并为一堆时所花代价
sum[i] = sum[i - 1] + a[i];
}
// 长度为 1 时不用合并,所以直接从 2 开始
// 枚举长度
for(int len = 2; len <= n; len ++) {
for(int l = 1; l <= n - len + 1; l ++) { // 状态:左端点
int r = l + len - 1; // 区间长度的右端点
dp[l][r] = 1e8; // 将最初的值设为最大,从而得到合并后的最小值
for(int k = l; k < r; k ++) { // k 为 分界点
dp[l][r] = min(dp[l][r],dp[l][k]+dp[k + 1][r]); // 我们的区间长度是从小到大的,所以可以做到不重不漏
}
// 将两堆合并为 1 堆时所付出的代价
dp[l][r] += sum[r] - sum[l - 1];
}
}
printf("%d\n",dp[1][n]);
return 0;
}
如果说年轻人未来是一场盛宴的话,那么我首先要有赴宴的资格。