19182 石子合并(基础版)
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC;JAVA
Description
设有 N(N≤300) 堆石子排成一排,其编号为1,2,3,⋯,N。每堆石子有一定的质量 mi(mi≤1000)。
现在要将这N堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻。
合并时由于选择的顺序不同,合并的总代价也不相同。试找出一种合理的方法,使总的代价最小,并输出最小代价。
输入格式
第一行,一个整数 N。
第二行,N 个整数 mi。
输出格式
输出仅一个整数,也就是最小代价。(题目确保答案在int范围)
输入样例
4
2 5 3 1
输出样例
22
解题思路
1. dp 方程定义
- dp[l][r] 表示从 L 到 R 合并成一堆的最小代价
2. 状态转移方程
此题 dp 思路是将所有区间以及需要的代价全部列出来,那么如何列出来呢?
- 对于 dp[l][r]:从 L 到 R 合并成一堆的最小代价,我们需要一个指针 mid,放在区间 [l, r] 中间,为什么需要这个 mid 呢?
目的就是将区间按多种情况来进行分割,以找到最符合要求的情况
因此转移方程可以为:dp[l][r] = min(dp[l][r], dp[l][mid] + dp[mid + 1][r] + s[r] - s[l - 1]);
- dp[l][mid] + dp[mid + 1][r] + s[r] - s[l - 1] 的意义
- 先把区间 [l, r] 切分为两部分 [l, mid] 和 [mid+1, r],mid 是切分点。
- 再把两部分 [l, mid] 和 [mid+1, r] 合并在一起。
- 注意在进行上一步的同时,还要加上 s[r] - s[l - 1],也就是合并的代价(通过前缀和来求,即区间和,这段区间加起来的总和)。
-
转移方程的初值:dp[i][i] = 0,因为合并每堆石子的代价为0,即自己跟自己合并相当于不用合,即最小代价为0
-
转移方程的最终结果:dp[1, n]:求合并第1堆到第 n 堆石子的最小代价
3. 算法解题思路
- 对 dp 数组进行初始化,每个值都初始化为较大的一个数 memset(dp, 0x3f, sizeof(dp));
- 首先进行数据输入,输入的同时记录前缀和 s[i] = s[i - 1] + a[i]; 以及给转移方程设立初始值 dp[i][i] = 0;
- 进行状态转移,我们目标是列出所有情况,即各个区间代价和。第一层循环变量为 len:区间长度,第二层循环为 l:区间起点,第三层循环为 mid,区间的分割点位置
- 每次循环时进行决策,跟之前算出过的代价和比较哪个更小,dp[l][r] = min(dp[l][r], dp[l][mid] + dp[mid + 1][r] + s[r] - s[l - 1]);
更多注释可查看下方的完整代码中,有助于理解
代码如下
#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
int a[301]; // 记录各石堆质量
int s[301]; // 前缀和数组
int dp[301][301]; // dp[i][j] 代表第 i 到 第 j 堆石子的最小代价
int main()
{
memset(dp, 0x3f, sizeof(dp));//先把 dp 里的所有数变成一个很大的数
int i, n;
cin >> n;
s[0] = 0; // 初始化用于后续记录
for(i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i]; // 记录前缀和
dp[i][i] = 0; // 自己跟自己合并相当于不用合,即最小代价为0
}
int len, l, r, mid;
// len 为区间长度,l(left) 为区间起点,r(right) 为区间终点
for(len = 2; len <= n; len++) {
// l的终点为最后一段区间的起点
for(l = 1; l <= n - len + 1; l++) {
r = l + len - 1;
// mid将区间 [l, r] 分成左右两段,所以 mid 不需要等于 r(分不了了)
for(mid = l; mid < r; mid++) {
// s[r] - s[l - 1] 为区间 [l, r] 的前缀和,即加起来的总和,合并的代价
dp[l][r] = min(dp[l][r], dp[l][mid] + dp[mid + 1][r] + s[r] - s[l - 1]);
//printf("dp[%d][%d]=%d, dp[%d][%d]=%d, dp[%d][%d]=%d s=%d\n", l, r, dp[l][r], l, mid, dp[l][mid], mid + 1, r, dp[mid + 1][r], s[r] - s[l - 1]);
}
}
//printf("len=%d\n\n", len);
}
cout << dp[1][r] << endl;
return 0;
}
最后
对我感兴趣的小伙伴可查看以下链接
- 我的掘金主页:https://juejin.cn/user/1302297507801358
- 博客主页:http://blog.zhangjiancong.top/
- 公众号:Smooth前端成长记录