首先我们要知道区间DP是怎么运作的,我个人的理解认为它的原理和归并排序类似,都是把一个大问题拆成了无数个小问题,最后通过得到小问题的最优解反过来求大问题的最优解。
题目:在一个圆形操场的四周摆放着n堆石子(n<= 100),现要将石子有次序地合并成一堆。规定每次只能选取相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。编一程序,读入石子堆数n及每堆的石子数(<=20)。求出做n-1次合并时,得分的最小值与最大值。
输入:4
4 5 9 4
输出:43
54
先附上代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int counts[210], dp1[200][200], dp2[200][200], mark[200] = {0};
int main()
{
int n, maxv=0, minv=999999;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> counts[i];
counts[i + n] = counts[i];//把环整成链,方便我们选取不同的起点
}
for (int i = 1; i <= 2 * n; i++)
{
mark[i] = mark[i - 1] + counts[i];//先求出石子的前缀和,这样在最后dp的时候,可以省去一次求和的过程。
}
memset(dp1, 0, sizeof(dp1));
memset(dp2, 999999, sizeof(dp2));
for (int i = 1; i <= 2 * n; i++)
{
dp1[i][i] = 0;//自己和自己是不能够相加的,因此得分为0;
dp2[i][i] = 0;
}
for (int length = 2; length <= n; length++)//枚举长度
{
for (int start = 1; start <= 2 * n - length + 1; start++)//枚举起点
{
int end = start + length - 1;//终点
for (int j = start; j < end; j++)//开始划分区间,选最优解。
{
dp1[start][end] = max(dp1[start][end], dp1[start][j] + dp1[j + 1][end] + mark[end] - mark[start - 1]);
dp2[start][end] = min(dp2[start][end], dp2[start][j] + dp2[j + 1][end] + mark[end] - mark[start - 1]);
}
}
}
for (int i = 1; i <= 2 * n; i++)
{
maxv = max(dp1[i][i + n - 1], maxv);
minv = min(dp2[i][i + n - 1], minv);
}
cout << minv << endl << maxv;
}
第一次看到这个dp式子的时候是很懵逼的,经过近乎3小时的努力,终于弄懂了一点。
说说我自己对这条dp式子的理解。
很容易理解的是,我们要求出第n-1次合并的最大最小值,就要知道第n-2次的最大最小值,第n-3次的最大最小值……以此类推。而我们每次合并的得分,与我们合并时选择的起点有关,例如我们求区间【1,8】的最大值时,就需要知道其更小区间的最大值,这个区间可能是【1,2】和【3,8】,也可能是【1,4】和【5,8】。这个就可以通过动态规划来从最小区间开始向上求出一个大区间的最优值。
当然对于这个题目来说,仅仅是相加两个区间的最优解是不正确的,我们还需要加上相应起点到终点的和,也就是前缀和,这样才是我们的最终得分,也是为什么我们dp式子最后要加上mark【end】-mark【start-1】。
举个例子:对于4 5 9 4这组数据,我们要求从4开始合并到最后的得分应该是:4+5+4+5+9+4+5+9+4=49分,即9+18+22。
本人铁菜鸟,有错欢迎指出!