石头合并问题
问题重现:
已知摆放着n堆石子,现要将石子有次序地合并成一堆。
规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
请设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
情形一:这n堆石头摆放成一列(首尾不相接)
情形二:这n堆石头摆放成一个圆(首尾相接,无首无尾)
情形一分析:
动态规划:
子问题状态定义:f[i,j]表示从第i个开始到第j个合并成一堆时的最大或者最小值,total[1,j]表示从第i个开始到第j个所有石头的数量。每次合并的是[k,k+1]
状态转移方程为:
f[i,j]=min{ f[i,k]+f[k+1,j]+total[i,j] 【k>=i && k<j && i<=j】}(下面只给出此种情况实现代码)
或者
f[i,j]=max{ f[i,k]+f[k+1,j]+total[i,j] 【k>=i && k<j && i<=j】}
特殊情况:
f[i,j]=0 【i==j】
f[i,j]=total[i,j]【i==j-1】
证明:
①为什么f[i,j]=f[i,k]+f[k+1,j]+total[i,j]
如从1到n之间,即f[1,n]。假设k处为最后二者合并的地方,即[k,k+1]最终合成一堆,这儿可以断定所以k>1且k<n,于是就可以分为[1,k]和[k+1,n]两堆,并且可以知道,在此之前肯定不会有[i,k]和[k+1,j]的堆合并,并且在合并的时候,显然[1,k]堆的数量是total[1,k],[k+1,n]堆的数量是total[k+1,n],最后总数为total[1,n]。
由此问题就转换为求f[1,k]和f[k+1,n],同上便可逐级递推得到。
②为什么k>=i && k<j
在①中已经提及过,这儿再次给出证明,因为每次合并的是[k,k+1],所以就有k+1<j,又如果出现[x,x],这时,便说明只有一堆了,也就是问题找到了过程解,所以就有k>=i && k+1<j
#include<stdio.h>
#define SIZE 4
/*求从第i到第j的和*/
int total(int i,int j,int des[]){
int total=0;
while(i<=j){
total+=des[i++];
}
return total;
}
/*递归计算核心函数(n堆石头摆放成一列)*/
int f(int i,int j,int des[]){
if(i==j){
return 0;
}
if(i==j-1){
return total(i,j,des);
}
int k,min,tmp;
min=f(i,i,des)+f(i+1,j,des);
for(k=i+1;k<j;k++){
tmp=f(i,k,des)+f(k+1,j,des);
if(min>tmp){
min=tmp;
}
}
return min+total(i,j,des);
}
void main(){
int des[]={2,5,3,4};
printf("最终结果为:%d\n",f(0,SIZE-1,des));
}
情形二分析:
类似情形一的证明,这儿还是要特别指出不同于情形一的地方,也就是i不需要大于j,而是一个环,也就是说i和j没有大小区分,但在计算时需要特殊处理(其实问题中包含了一个约瑟夫环)。
简便而又容易理解的方法:
现在换个方法来想此问题
如序列1、3、4、2、1
其实每次只需要搜索邻接和最小的一组数,然后合并成一个数;接着重复前面的操作,直到只剩一个数,也就得到了最终的解。