区间dp模型分析-石子合并
石子合并
原题链接:传送门
分析
通过阅读题干,我们可以获取以下关键信息。
- 每一次能合并相邻的两堆石子
- 且我们希望最终合并成一堆的代价尽可能的小
对于这个问题发现正着思考比较困难,我们尝试倒着去分析它,我们发现最终要合并成一堆石子那么在合并成最后一堆石子之前一定是两堆已经划分好了的且消耗代价尽可能小的两堆石子进行合并
故我们可以进行分隔法,将石子分割为若干个这样的左右区间(左右石子堆),这样的区间可以划分为n - 1个(如下图所示)
即可以分为 \(n - 1\)个\([l , k] , [k + 1 , r]\) 左右区间我们只需要遍历着n - 1个区间求出最优的划分点 k 时,所消耗的最小代价即可。
当然这是考虑的最后一个状态的时候即\(f(1 , n)\) 表示将区间\([1-n]\) 合并成一堆所消耗的最小代价
那么对于已经划分的左右两边区间分别求出\(f(l , k) + f(k + 1 , r) + cost(l , r)\) 即可求出最终答案
故我们得到状态计算方程:
\(f(l , r) = min(f(l , r) , f(l , k) + f(k + 1 , r) + cost(l , r)) 当 l \not= r\)
\(f( l ,r) = 0 , 当 l = r\)
伪代码
int f(int l , int r)
{
if l == r : 返回 0
// 将其划分为 n - 1个区间
for(int k = l , k < r ;k ++)
{
f(l , r) = min(f(l , r) , f(l , k) + f(k + 1 , r) + cost(l , r))
}
return 进行记忆化和状态压缩
}
递归代码
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int f[N][N] , a[N] , n;
// 确定状态 f[l][r]
// 表示将 [l , r] 区间内的石子合并成一堆所消耗的最小的代价
// 很明显 f[i][i] = 0 , 当 i 属于[l ,r]
// 而f(i , j) = min(f(i , j) , f(l , k) + f(k + 1 , r) + cost(l , r));
// cost(l ,r) 表示将其从[l , r]合并为一堆一定会消耗的代价,为l,r的前缀和
int Sum(int l, int r)
{
return a[r] - a[l-1];
}
int dp(int l , int r)
{
if(f[l][r] != -1)return f[l][r];
if(l == r)return f[l][r] = 0; // 确定边界
// 划分为 n - 1 个区间
int ans = 0x3f3f3f;
// 将其划分为 n - 1 个区间
for(int k = l;k < r;k ++){
ans = min(ans , dp(l , k) + dp(k + 1 , r) + Sum(l , r));
}
return f[l][r] = ans;
}
int main()
{
cin >> n;
for(int i = 1;i <= n ;i ++)
cin >> a[i];
// 初始化和预处理前缀和
memset(f , -1 , sizeof f);
for(int i = 1;i <= n ;i ++)
a[i] += a[i-1];
cout << dp(1 , n) << endl;
return 0;
}
递推版本
我们已经分析过递归的代码,思路是从后往前进行划分区间,将大问题拆分成一些子问题进行诸葛解决。
那么对于递推我们通常是从已经划分好的子问题入手,逐步合并至最后一个区间
我们设置一个长度表示每次从每个端点开始 枚举阶段(区间)长度-->确定状态-->最后进行决策依次递推得到最终结果。
貌似按照长度枚举是一个模板 那就记下来吧!
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int a[N] , f[N][N];
int sum(int l,int r)
{
return a[r] - a[l-1];
}
int main()
{
int n;cin >> n;
for(int i = 1;i <= n ;i ++)cin >> a[i];
memset(f , 0x3f , sizeof f);
for(int i = 1;i <= n ;i ++) // 预处理前缀和
{
a[i] += a[i-1] , f[i][i] = 0;
}
for(int len = 2; len <= n; len++)
{
for(int l = 1;l <= n - len + 1 ;l ++)
{
int r = l + len - 1;
for(int k = l;k < r ;k ++)
f[l][r] = min(f[l][r] , f[l][k] + f[k + 1][r] + sum(l , r));
}
}
cout << f[1][n] << endl;
return 0;
}