区间dp就是利用了分治的思想,将整个区间不断的拆分一下,将一个区间[l,r]分成[l,k] [k+1,r],然后再对[l,k]和[k+1,r]进行类似的拆分,直到拆分成最小的区间,通过对每个小区间算出最小或者最大代价,再通过将这些小区间组合成一个更大的区间,哪种组合方式最优,则就是所求解。
那么下面用一个典型的例题来解释一下
合并石子
这道题算是一道很经典的题了
那么先把代码列出来
#include <iostream>
#include <cstring>
using namespace std;
const int N=310;
int n;
int a[N];
int s[N];
int f[N][N];
int int main(int argc, char const *argv[])
{
memset(f,0x3f,sizeof(f));
cin>>n;
for (int i = 1; i <=n; ++i)
{
cin>>a[i];//每堆石子的质量
s[i] = s[i-1]+a[i];//求前缀和
f[i][i] = 0;//合并每一堆石子的代价为0
}
for (int len = 2; len<=n; len++)
{
for (int l=1; l+len-1 <=n; ++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]+s[r]-s[r-1]);
}
}
}
cout<<f[1][n]<<endl;
return 0;
}
关键的代码在这三层for循环上面
第一层for循环,刚才讲过我们要分而治之,所以 要分区间,那么我们可以选择将一个大区间分成若干个小区间,那么分的标准是什么呢?这就是第一层for循环的作用,len就是每次分的区间长度,比如每次把1到5分成如下图所示
然后第二层循环就是定义区间的左端点,l=1,再根据len=2,就能确定区间为1,2,即f[1][2] ,
然后第三层循环就是拆分了,如上图,len=2的时候是无法拆分了,但是len=3的时候是可以拆成f[1][2],f[3][3],f[1][1],f[2][3],然后再如len=4时,拆分如下图
如我刚开始所说,就是将很多较小的区间组合成一个较大的区间,然后这些组合哪个代价最小则就是最优解,如上图有三种组合方式,我们将一个区间[l,r]分成[l,k] [k+1,r],第三层循环就是在变化这个k的,还是如上图,很清晰了
会拆分区间了,那么我们如何去算转移方程呢?我们将两个区间 1,2 &3,4
这两个区间合并,那么就是1,2的代价加上3,4的代价,再加上1到4的区间和,具体的式子如下
f[1][4] = min(f[1][4],f[1][2]+f[3][4]+s[4]-s[0]);
为什么这么计算呢,因为你要知道你在求解的过程中是通过小的区间组合成较大的区间的,而你所用到的这两个小的区间f[1][2]+f[3][4],已经得出了最优解了,那么得出最优解需不需要花费代价呢?当然需要,所以我们需要加上那段花费,那么简单来说就是
f[1][2]+f[3][4] = s[2]-s[0]+s[4]-s[2]
//那么更加直接的式子是
f[1][4] = min(f[1][4],s[2]-s[0]+s[4]-s[2]+s[4]-s[0]);
//你会发现,会有很多重复值,就是因为这样的计算会加上很多重复的值,而如果加的重复值更小,则结果会更优
而s[4]-s[0]就是你合并两个堆就是里面所有数的总和,f[1][2]+f[3][4]这两个堆的总和其实就是f[1][4]区间内所有数的总和,那么用前缀和算出来就可以了
那么我们来走一遍题的流程,提前说明一下,我们应该先把那些 1,1 ||2,2这种不需要移动的,设置为0,因为不需要移动,而其他值设置为INF,为了我们可以求min
len = 2
for (int len = 2; len<=n; len++)
{
for (int l=1; 1+2-1 <=5; ++l)
{
int r = l+len-1;
for (int k=1; k<2 ; k++)
{
f[1][2] = min(f[1][2],f[1][1]+f[2][2]+s[2]-s[0]);
//那么结果就是s[2]-s[0] =4
}
}
//当l=2时,f[2][3] = min(f[2][3],f[2][2]+f[3][3]+s[3]-s[1]);
//结果就是s[3]-s[1] = 7,同理可以看下面图中的结果
}
解释一下图中对应的 第一列是f[l][r] 第二列是f[l][k]第三列是f[k+1][r]第四列是s[r]-s[r-1]的结果
当len = 3时
for (int len = 2; len<=n; len++)
{
for (int l=1; 1+3-1 <=n; ++l)
{
int 3 = l+len-1;
for (int k=1; k<3 ; k++)
{
f[1][3] = min(f[1][3],f[1][1]+f[2][3]+s[3]-s[0]);
//结果就是min(INF,0+7+8) = 15
}
//接着k=2
for (int k=2; k<3 ; k++)
{
f[1][3] = min(f[1][3],f[1][2]+f[3][3]+s[3]-s[0]);
//结果就是min(INF,0+4+8) = 12
那么f[1][3] = 12
//其他情况同理如下图
}
}
}
所有的结果就如下了