题目描述
在一个圆形操场的四周摆放着 n堆石子。 现要将石子有次序地合并成一堆。 规定每次选2 堆石子合并成新的一堆,合并的费用为新的一堆石子数。试设计一个算法,计算出将 n堆石子合并成一堆的最小总费用。
输入
输入数据第1行有1个正整数 n(1≤n≤1000),表示有 n堆石子,每次选2堆石子合并。第2行有 n个整数, 分别表示每堆石子的个数(每堆石子的取值范围为[1,1000]) 。
输出
数据输出为一行, 表示对应输入的最小总费用。
样例输入
7
45 13 12 16 9 5 22
样例输出
313
分析:
石子合并问题有三种版本,第一种是任意两个堆石子合并,第二种是一条直线的相邻的两堆石子合并,这里是第三种,圆形相邻石子合并。
针对第一种,很明显用哈夫曼树即贪心的方法即可解决。第二种用动态规划的思路。这里讲讲第三种问题的思路:首先就是怎么解决圆形的问题,我们可能会想到化曲为直,那么怎么把圆形问题转化为直线问题?
假设操场有5堆石子,如图:
我们假设最小花费的合并方式如下图:
至此,我们可以看到,我们不必把这些石子连成一个环就可以解决该问题,其实质上就是直线上的石子合并问题。那么接下来的问题就是,哪一种花费最小?我们知道,n堆石子,可以化成n条直线问题,取最小值即可。下面就是对于每一条直线问题,我们如何解决。第一点,我们要明确这条直线是从哪里开始的;第二点,我们要明确直线包括了哪些石子。即起点和长度。针对第二种问题,我们可以用起点到终点来表示,但是对于环形而言,直线就转化为0->4, 1->0, 2->1, 3->2, 4->3,用数组不好表示。对于起点和长度,我们可以用二维数组,两个坐标分别表示起点和长度。比如dp[1][5]表示从编号为1的石堆开始,合并从1开始的5个石堆所需要的最小花费。
然后就需要找状态转移方程了,这里定义一个函数int getSum(int start, int step)表示从start开始的step个石堆总共的石子数量。方程如下:
dp[i][n]是指从i起的n堆石子,dp[i][k]是从i起的k堆,k到k+1为间断点,还剩下n - k堆,是从编号为(i + k) % n的石堆开始的,合并这两堆的费用是getSum(i, n)。步长为1时,花费为0。从步长为2开始,对于每条直线,计算dp[i][n]的最小值,直到n == N(总共的堆数)。dp[i][N]的最小值即为所求。
#include<bits/stdc++.h>
using namespace std;
int n;
int stone[1010];
int dp[1010][1010];
int getSum(int start, int step){
int sum = 0;
for(int i = 0; i < step; i++)
sum += stone[(start + i) % n];
return sum;
}
int main(){
while(cin >> n){
for(int i = 0; i < n; i++){
cin >> stone[i];
dp[i][1] = 0;
}
for(int t = 2; t <= n; t++){
for(int i = 0; i < n; i++){
int sum = getSum(i, t);
dp[i][t] = INT_MAX;
for(int k = 1; k < t; k++)
dp[i][t] = min(dp[i][t], dp[i][k] + dp[(i + k) % n][t - k] + sum);
}
}
int ans = dp[0][n];
for(int i = 1; i < n; i++)
ans = min(ans, dp[i][n]);
cout << ans << endl;
}
return 0;
}
/*
input:
7
45 13 12 16 9 5 22
output:
313
*/