【问题描述】
在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数。求把所有石子合并成一堆的最小花费。
【输入】
输入第一行包含一个整数n,表示石子的堆数。接下来一行,包含n个整数,按顺序给出每堆石子的数量大小 。
【输出】
输出一个整数,表示合并的最小花费。
【样例输入】
5
1 2 3 4 5
【样例输出】
33
【样例说明】
输入:一共有5堆石子,数量分别为1,2,3,4,5.
输出:最小花费为33.
给出【问题分析】、【递归公式及解释】,以及【根据样例得到的最优值的解释】。
问题分析:
这是一道动态规划中的区间DP问题。
引入dp、p、s数组
dp数组:最优化矩阵 dp[i][j]:表示第i堆到第j堆石子合并的最优值
p数组:最优化位置矩阵 p[i][j]:表示第i堆到第j堆石子合并时,最优位置是p[i][j]
s数组:记录石头的数量:s[i][j]表示第i堆到第j堆石子的总数量
如果只有一堆石子,不需要合并,费用为0。
如果有两堆石子,需要一次合并,费用为两堆石子数量之和。
如果有三堆石子,需要两次合并,费用为第1堆和第2堆合并以及第2堆和第3堆合并费用中的较小值,加上三堆石子数量。
如果有n堆石子,需要n-1次合并,可用k进行分隔,分为第1到第k堆和第k+1到第n堆,(k从1取到n-1),两部分分别求合并费用,k取使两部分的合并费用总和最小的那个值,最后再加上所有石子数量总和,即为整体的合并费用。
公式及解释:
dp[i][j]=0 i=j
dp[i][j] = min( dp[i][j], dp[i][k] + dp[k+1][j] + s[i][j] ) i≠j
dp[i][j]的最优解一定在i到j中间的两个最优解的和,再加上本次费用。
根据样例得到的最优解的解释:
33
最小花费33
dp矩阵如下:
0 3 9 19 33
0 0 5 14 28
0 0 0 7 19
0 0 0 0 9
0 0 0 0 0
再结合p矩阵
0 1 2 3 3
0 0 2 3 3
0 0 0 3 4
0 0 0 0 4
0 0 0 0 0
合并步骤为:
1和2合并得3,当前花费为3;
3和3合并得6,当前花费为6,总花费为9;
4和5合并得9,当前花费为9,总花费为18;
6和9合并得15,当前花费为15,总花费为33。
代码实现:
#include <bits/stdc++.h>
using namespace std;
int n, a[100];
int dp[100][100]; //dp[i][j]表示从第i堆到第j堆合并的费用
int s[100][100]; //石头的数量
int p[100][100]={0};
int merge (int n,int a[]){
for (int i = 1; i <= n; i++)
{
s[i][i] = a[i];
dp[i][i] = 0;
}
//区间长度
for (int len = 2; len <= n; len++)
{ //左端点 右端点最大不能超过n
for (int i = 1; i + len - 1 <= n; i++)
{ //右端点
int j = i + len - 1;
//先将代价设为最大
dp[i][j] = 1000000;
for (int k = i; k <= j; k++) //用k来分割区间
{
s[i][j] = s[i][k] + s[k + 1][j];
if(dp[i][j]>dp[i][k] + dp[k + 1][j] + s[i][j])
{
dp[i][j]=dp[i][k] + dp[k + 1][j] + s[i][j];
p[i][j]=k;
}
}
}
}
cout << dp[1][n] << endl;
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
merge(n,a);
//输出最优化矩阵dp
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<dp[i][j]<<' ';
}
cout<<endl;
}
//输出最优位置矩阵p
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<p[i][j]<<' ';
}
cout<<endl;
}
return 0;
}