问题描述:
在一个圆形操场的四周摆放着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;
}