前面章节已经讲到背包问题,硬币问题,这期继续给大家带来DP问题中的区间DP。
区间DP其实指的是在小区间内部进行DP得到小区间的最优解,将小区间逐渐合并来得到大区间的最优解。在解决区间DP问题我们通常会利用模版利用for循环枚举全部可能区间,然后分析状态转移方程。
给大家看一道经典例题:
有n堆石子排成一排,每堆石子有一定数量,将n堆石子合并成一堆,合并的过程只能合并相邻的两堆石子,合并所需的代价为两堆石子的总数,求合并成一堆的最小花费。
输入样例:
3
2 4 5
输出样例:
17
咱们先来分析样例,2 4 5 ,先合并 2 + 4 = 6,然后6 + 5 = 11,所以总花费为6 + 11 = 17。
首先研究dp数组的含义。我们分析题干,将两个相邻的石子合并,先从最末端开始,最后的操作肯定是剩下两堆石子进行相加,所以这里咱们设置一个k来枚举将一堆拆成两堆的所有情况,咱们设dp[i][j],dp数组的含义为从第i堆石子到第j堆石子的最小花费。(由整化零)那么分析他的状态转移方程:
1.最开始的dp[i][i]为第i堆石子,没有合并,所以dp[i][i] = 0
2.接下来分析j = i + 1的情况下的两堆合并:首先对于第一堆和第二堆分析->dp[1][2] = dp[1][1] + dp[2][2] + s[2] - s[1].
(这里的是s数组为前缀和代表含义为s[i]是前i个元素的和),1到2堆的最小花费=第一堆的最小花费+第二堆的最小花费+1到2之间的数字和
3.若i与j不相邻,那么咱们就要一堆化两堆:dp[i][j] = min(dp[i][j],dp[i][k] + dp[k + 1][j] + s[j] - s[i])
举个例子:1 2 3 4 5 6 7
i->1,j->7,那么想将这一堆拆分两堆,所有的情况是不是从k = 1开始枚举到k = 6,类似于k是刀,要将这一条线割成两条线的所有的方案:
for(int k = i;k < j;k++)
dp[i][j] = min(dp[i][j],dp[i][k] + dp[k + 1][j] + s[j] - s[i];
那么接下来就是遍历顺序,首先枚举i到j的距离,然后逐渐枚举i并不断刷新j使其可以包括所有可能的情况区间。
for(int len = 1;len <= n;n++)
{
for(int i = 1;i + len <= n;i++)
{
int j = i + len;
dp[i][j] = 1e9;
for(int k = i;k < j;k++)
{
dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k + 1][j] + s[j] - s[i]);
}
}
}
那么请看完整代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int n;
int s[N];//前缀和
int dp[N][N];
int main()
{
cin >> n;
//计算前缀和
for(int i = 1;i <= n;i++)
{
s[i] += s[i - 1];
}
for(int len = 1;len <= n;n++)
{
for(int i = 1;i + len <= n;i++)
{
int j = i + len;
dp[i][j] = 1e9;
for(int k = i;k < j;k++)
{
dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k + 1][j] + s[j] - s[i]);
}
}
}
cout << dp[1][n] << endl;
return 0;
}
其实在分析过程中,我们可以发现k的枚举会有重复多余的部分,每次运行区间内部都是为了找最优的k值,那么我们可以记录当前的k值,以此来达到避免重复运算。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int n;
int s[N];//前缀和
int dp[N][N];
int good[N][N];//存放k的最优值
int main()
{
cin >> n;
//计算前缀和
for(int i = 1;i <= n;i++)
{
s[i] += s[i - 1];
}
for(int i = 1;i <= n;i++)
{
good[i][i] = i;
}
for(int len = 1;len <= n;n++)
{
for(int i = 1;i + len <= n;i++)
{
int j = i + len;
dp[i][j] = 1e9;
for(int k = good[i][j - 1];k <= good[i + 1][j];k++)
{
dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k + 1][j] + s[j] - s[i]);
s[i][j] = k;//记录
}
}
}
cout << dp[1][n] << endl;
return 0;
}
好了,本期内容就到这里了,感谢收看,记得三连支持哈。