石子合并的动态规划解决方案

问题描述: 

在一个圆形操场的四周摆放着n 堆石子。现将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。

动规合并过程:

以序列: 9 6 7 9 20 为例:

第一次合并: 15 7 9 20 //9,6合并 score = 15

第二次合并: 15 16 20 //7,9合并 score = 16

第三次合并: 31 20 //15,16合并 score = 31

第四次合并: 51 //31,20合并 score =50

得分 15+16+31+51= 113

最优解结构的探讨:

假设有 i,i+1,i+2,i+3,i+4,…,i+j 个石头堆合并。

则有合并方式有

[ (i) (i+1,i+2,i+3,...,i+j) ]

[ (i,i+1) (i+2,i+3,...i+j) ]

[ (i,i+1,...,i+j-1) (i+j) ]

以G[i,j]表示从i 合并了j个元素的得分,则G[i,j] ==min{G[i,k] + G[k+1,j-k]}+ total(i,j); i<k<i+j;  

其中total(i,j)为最后一次合并的得分 为石子的总数;

而 G[i,k] , G[k+1,j-k]的总得分 ,也用上式求出。

因为动态规划是根据小问题的解来得到大问题的解。因此我们可以先两两合并,然后三个,然后四个合并最后n堆合并。

序列 9 6 7 9 20 的合并过程如下,

第一列表示合并1个土堆,第二列表示合并2个土堆……

第一行表示从第一个土堆开始,第二行表示从第二个土堆开始。


我们需要的是最后一行的最值。


#include<cstdio>
#include<cstdlib>
#include<iostream>
 using namespace std;
#define MAX_SIZE 110

int array[MAX_SIZE];  //每个土堆的数量
int dp[MAX_SIZE][MAX_SIZE];//局部最优解的集合

int cmptosmall(int a,int b){
		return a <= b;
}
int cmptolarge(int a,int b){
	return a >= b;
}
int getsum(int begin,int end,int n){ // 求 array[begin] 到 array[brgin+end-1] 的和
	int sum = 0;
	int i;
	for(i = begin; i < begin+end; i++)
		sum += array[(i-1)%n+1];
	return sum;
}
int merge(int n,int (*compare)(int,int)){ // 以函数指针来确定找最大还是最小
	int i,j,k,temp;
	for(i = 1; i <=n; i++)
		dp[i][1] = 0;  //只有一堆未移动赋值为0

/*使用公式G[i,j]  ==  G[i,k]+G[k+1][j] + total(i,j)来求解
	for(j = 2; j <= n; j++){ //合并的数量
		for(i = 1; i <= n; i++){ //以 i 为底将 i 之后的 j 堆石头 合并入 i
			dp[i][j] = dp[i][1] + dp[(i+1-1)%n+1][j-1]; //+ getsum(i,j); //以第一种分法做最优解
			for(k = 2; k <= j-1; k++){  //遍历结合的所有其他可能 更新成最小值 ,得到dp[i][j]最优解
				temp = dp[i][k] + dp[(i+k-1)%n+1][j-k]; //+ getsum(i,j); 与上面的相同,可以先删除最后加上
				//if(temp < dp[i][j])
				if(compare(temp,dp[i][j]))
					dp[i][j] = temp;
			}
			dp[i][j] += getsum(i,j,n); //前面没有加sum在此处加上
		}
	}
	int bestgrade = dp[1][n]; //以 dp[1][n]做最优解
	for( i = 2; i <= n; i++){ //更新最优解
		//if(minn > dp[i][n])
		if(!compare(bestgrade,dp[i][n]))
			bestgrade = dp[i][n];
	}
/*
	for(i = 1; i <= n ; i++){ //输出所有局部最优解
		for(j = 1; j <= n; j++){
			printf("%5d",dp[i][j]);
			}
			cout<<endl;
		}
*/
	return bestgrade;
}
 int main(void){
	freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
	int n,i;
	 //cin>>n;
	 scanf("%d",&n);
	 for(i = 1; i <= n; i++) cin>>array[i];

	// cout<<"array[n]="<<array[n]<<endl;
	int minn =  merge(n,cmptosmall);
	cout<<"minn == "<<minn<<endl;
	int maxn =  merge(n,cmptolarge);

	cout<<"maxn =="<<maxn<<endl;
	 return 0;
	 }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值