石子合并三大题型
- 任意两堆石子合并,直接手写一个小根堆,每次取前面两个相加,得到的值继续放入优先队列中,直到里面只有一个元素就输出
- 只能合并相邻两堆石子,这就类似于矩阵连乘(传送门),不过还是有一些差别
- 在2的基础上,这些石子形成一个环形,这又是一种新的题型
今天记录的是第二种——区间dp
题目描述
在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数。求把所有石子合并成一堆的最小花费。例如:输入{1,2,3,4,5},输出33。【3+6+9+15=33】
输入
本题应该处理到文件尾,每组输入包括两行,第一行为石子堆的个数n,第二行则为每堆石子的个数。
输出
输出最小花费。
样例输入
5
1 2 3 4 5
样例输出
33
ps:
首先分析一下这个题目,举个例子,如果只有三堆 4 , 5 , 9
我们合并这三堆所花费的来自三部分
- 第一二堆合并的花费
- 第一二堆石子的个数和
- 第三堆石子的数量
- 那么就是4+5+9+6=24
但是这个题目要求的是相邻两堆之间合并,如果又很多堆石子,那么我们就要去找断点,最优值问题就是一个个最优子结构,所以我们需要一个一个去找断点,算出每一个最优子结构,然后相加
转移方程如下
此外我们需要引入一个数组,来记录石子的数量,可以利用前缀和思想,也可以直接存储第i到第j石子的数量
前缀优化
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 1e3 + 5;
int a[maxn], b[maxn], dp[maxn][maxn];
int main()
{
int n;
while (~scanf("%d", &n))
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]), b[i] = b[i - 1] + a[i]; //b数组求前缀
for (int l = 2; l <= n; ++l)
{
for (int i = 1; i + l - 1 <= n; ++i)
{
int j = i + l - 1;
dp[i][j] = inf;
int x = b[j] - b[i - 1]; //i到j区间的石子数
for (int k = i; k < j; ++k)
{
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + x);
}
}
}
printf("%d\n", dp[1][n]);
}
return 0;
}
利用数组存取石子的数量,和矩阵相乘类似,只是把乘换成加了
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
//#define inf 1<<20
const int maxn = 210;
int n, a[maxn];
int dp[maxn][maxn]; //dp[i][j]表示从第i堆到第j堆合并的代价
int sum[maxn][maxn]; //表示石头的数量
int main()
{
while (cin >> n)
{
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
sum[i][i] = a[i], dp[i][i] = 0;
for (int len = 2; len <= n; len++)
{
//区间长度
for (int i = 1; i + len - 1 <= n; i++)
{
//区间起点
int j = i + len - 1; //区间终点
dp[i][j] = inf;
for (int k = i; k <= j; k++) //用k来表示分割区间
{
sum[i][j] = sum[i][k] + sum[k + 1][j];
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[i][j]);
}
}
}
cout << dp[1][n] << endl;
}
return 0;
}
人生如一场修行,得意时, 一日看尽长安花,艰难时,潦倒新停浊酒杯,但生命的跋涉不能回头,哪怕,畏涂巉岩不可攀,也要会当临绝顶,哪怕无人会登临意,也要猛志固常在,从经典中汲取九万里风鹏正举的力量,历练也无风雨也无晴的豁然,待到重阳日,我们还来就菊花。 |
---|
若干年看到这些图片不知会作何感想