Description
在操场上沿一直线排列着 n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的两堆石子合并成新的一堆, 并将新的一堆石子数记为该次合并的得分。允许在第一次合并前对调一次相邻两堆石子的次序。
计算在上述条件下将n堆石子合并成一堆的最小得分。
Input
输入数据共有二行,其中,第1行是石子堆数n≤100;
第2行是顺序排列的各堆石子数(≤20),每两个数之间用空格分隔。
Output
输出合并的最小得分。
Sample Input
3 2 5 1
Sample Output
11
相信大多数人看到这道题第一反应就是直接贪心,每次都选最小的两堆合并,每次都是最小那最后不也是最小,但实际上每次的最优解并一定是最终的最优解,因为你无法预知接下来剩下的合成解是否也是最优
换一个角度,最后一次合并就是把上一次合并剩下的两堆合并成一堆,再加上这个区间的和,再来观察这两个小堆其中一个,是不是跟最后和并的大堆实际上是一个东西,而我们是不是只要保证两个小堆的合成是最优,那么合并出来的大堆也就是最优,那么如何划分这两个小堆,那就是枚举划分点的位置x,把f[i][j]设为i到j的最优合并费用,可以得到f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+det),det就是i到j的区间和,因为把两个区间合并就是加上这两个区间的总和。
这一题的开始可以交换位置,这个好办,就枚举交换位置的数好了,不要忘记换回来。
下面代码和一些细节
#include<stdio.h>
#include<algorithm>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define r(i,a,b)for(int i=1;i<=b;i++)
ll f[110][110];
ll sum[110];
int a[110];
int n;
void swap(int x, int y)
{
int t;
t = a[x], a[x] = a[y], a[y] = t;
return;
}
void ini()
{
r(i, 1, n)
r(j, 1, n)
{
if(i==j)f[i][j]=0;//跟自己合并费用为零
else f[i][j]=inf;//初始值设为无穷大
}
return;
}
int main()
{
scanf("%d", &n);
r(i, 1, n)scanf("%d",a+i);
ll ans = inf;
r(wz, 1, n - 1)
{
swap(wz, wz + 1);//交换两个相邻的位置
ini();//对f数组初始化
r(i, 1, n)sum[i] = a[i] + sum[i - 1];//前缀和辅助快速求区间和
//r(i,1,n)printf("%lld ",sum[i]);printf("\n");
r(l, 1, n - 1)//长度-1
{
for (int j = 1; j + l <= n; j++)
{
int k = j + l;//右边界
ll det = sum[k] - sum[j - 1];
//合并这一次必定要加上这个区间的和,两个区间合成一个长区间,得到这个区间和分数
r(x, j , k-1)//枚举拆分数组,123 45这样拆成两个部分
f[j][k] = min(f[j][k], f[j][x] + f[x + 1][k] + det);
}
}
//printf("%lld\n",ans);
ans = min(ans, f[1][n]);//计算交换这一轮下的最小值
swap(wz, wz + 1);//换回来
}
printf("%lld\n",ans);
return 0;
}