问题描述:
??在一个园形操场的四周摆放N堆石子(N≤100),现要将石子有次序地合并成一堆。规定
????? 每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
????? 编一程序,由文件读入堆数N及每堆的石子数(≤20),
????? ①选择一种合并石子的方案,使得做N-1次合并,得分的总和最小;
????? ②选择一种合并石子的方案,使得做N-1次合并,得分的总和最大。
????? 例如,所示的4堆石子,每堆石子数(从最上面的一堆数起,顺时针数)依
????? 次为4594。则3次合并得分总和最小的方案:8+13+22=43
????? 得分最大的方案为:14+18+22=54
该问题应该用动态规划算法才能得到最优解,而贪心算法得不到最优解。
而动态规划算法的基本要素是:最优子结构和重叠子问题
针对该问题,我们用f(i,j)表示从第i堆石子数起,顺时针数j堆石子的得分,则它的动态规划方程为
?当求最大得分总和时
????? f〔i,j〕=max{f〔i,k〕+f〔x,j-k〕+t}
????? 1≤k≤j-1
????? c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
????? (2≤j≤n,1≤i≤n)
????? 当求最小得分总和时
????? f〔i,j〕=min{f〔i,k〕+f〔x,j-k〕+t}
????? 1≤k≤j-1
????? c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
????? (2≤j≤n,1≤i≤n)
????? 其中x=(i+k-1)modn+1,即第i堆数起,顺时针数k+1堆的堆序号。
?? ? 对每一堆石子来说,它的
????? f〔i,1〕=0
????? c〔i,1〕=0 (1≤i≤N)
????? 对于子序列〔i,j〕来说,若求最小得分总和,f〔i,j〕的初始值为∞; 若求最大得分总和,f〔i,j〕的初始值为0。(1≤i≤N,2≤j≤N)。
例如对例子中的6(3 4 6 5 4 2 )堆石子,按动态规划方程顺推最小得分和。 依次得出含二堆石子的6个子序列的合并方案
????? f〔1,2〕=7 f〔2,2〕=10 f〔3 ,2〕=11
????? c〔1,2〕=1 c〔2,2〕=1 c〔3,2〕=1
????? f〔4,2〕=9 f〔5,2〕=6 f〔6,2〕=5
????? c〔4,2〕=1 c〔5, 2〕=1 c〔6,2〕=1
????? 含三堆石子的6(3 4 6 5 4
2 )个子序列的合并方案
????? f〔1,3〕=20 f〔2,3〕=25 f〔3,3〕=24
????? c〔1,3〕=2 c〔2,3〕=2 c〔3,3〕=1
????? f〔4,3〕=17 f〔5,3〕=14 f〔6,3〕=14
????? c〔4,3〕=1 c〔5,3〕=1 c〔6,3〕=2
????? 含四堆石子的6(3 4 6 5 4
2 )个子序列的合并方案
????? f〔1,4〕=36 f〔2,4〕=38 f〔3,4〕=34
????? c〔1,4〕=2 c〔2,4〕=2 c〔3,4〕=1
????? f〔4,4〕=28 f〔5,4〕=26 f〔6,4〕=29
????? c〔4,4〕=1 c〔5,4〕=2 c〔6,4〕=3
????? 含五堆石子的6(3 4 6 5 4
2 )个子序列的合并方案
????? f〔1,5〕=51 f〔2,5〕=48 f〔3,5〕=45
????? c〔1,5〕=3 c〔2,5〕=2 c〔3,5〕=2
????? f〔4,5〕=41 f〔5,5〕=43 f〔6,5〕=45
????? c〔4,5〕=2 c〔5,5〕=3 c〔6,5〕=3
????? 含六堆石子的6(3 4 6 5 4
2 )个子序列的合并方案
????? f〔1,6〕=61 f〔2,6〕=62 f〔3,6〕=61
????? c〔1,6〕=3 c〔2,6〕=2 c〔3,6〕=2
????? f〔4,6〕=61 f〔5,6〕=61 f〔6,6〕=62
????? c〔4,6〕=3 c〔5,6〕=4 c〔6,6〕=3
????? f〔1,6〕是f〔1,6〕,f〔2,6〕,……f〔6,6〕中的最小值,表明最小得分和是由序列〔1,6〕经5次合并得出的。我们从这个序列出发,
????? 按下述方法倒推合并过程:
????? 由c〔1,6〕=3可知,第5次合并的两堆石子分别由子序列〔1,3〕和子序列〔4,3〕经4次合并后得出。其中c〔1,3〕=2可知由子序列〔1,3〕合并成的一堆石子是由子序列〔1,2〕和第三堆合并而来的。而c〔1,2〕=1,以表明了子序列〔1,2〕的合并方案是第1堆合并第2堆。
????? 由此倒推回去,得出第1,第2次合并的方案,每次合并得分
????? 第一次合并 3 4 6…… ->7
????? 第二次合并 7 6……?????????
->13
????? 13……
????? 子序列〔1,3〕经2次合并后合并成1堆, 2次合并的得分和=7+13=20。
????? c〔4,3〕=1,可知由子序列〔4,3〕合并成的一堆石子是由第4堆和子序列〔5,
????? 2〕合并而来的。而c〔5,2〕=1,又表明了子序列〔5,2〕的合并方案是第5堆合并第6堆。由此倒推回去,得出第3、第4次合并的方案
????? 每次合并得分:
?????? 第三次合并 ……54 2???? ->6
????? 第四次合并 ……5 6??????? ->11
????? ……11
????? 子序列〔4,3〕经2次合并后合并成1堆,2次合并的得分和=6+11=17。
????? 第五次合并是将最后两堆合并成1堆,该次合并的得分为24。
????? 显然,上述5次合并的得分总和为最小
????? 20+17+24=61
下面分析下代码的实现过程
?1.初始化grades和ks的值
??/*
初始化Grades和ks的值 choice为1表示求最多得分,为0为求最少得分 n表示有n堆石子 */ void init(int **grades,int **ks,int choice,int n){ //给计算得分的数组赋初值 if(choice==1){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ grades[i][j]=0; ks[i][j]=0; } } }else{ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(j==1){ grades[i][j]=0; }else{ grades[i][j]=999999; } ks[i][j]=0; } } } }
??2.计算子问题的最少得分
??/*
计算从start堆开始,计算count堆石子的最少得分 stoneCounts储存所有堆的石子数 leastGrades[start,count]对应的最少得分 ks[start,count]记录第start到第start+count间的k值,1=<k<=(count-1) */ void getMiniGrade(int *stoneCounts,int **leastGrades,int **ks,int start,int count,int n){ if(count>1){ int countStones=0; //计算石子数 for(int j=0;j<count;j++){ countStones=countStones+stoneCounts[start+j]; } int grade=999999; for(int i=1;i<count;i++){ int k=(start+i-1)%n+1; // cout<<" 中间值:"<<i<<">>>>"<<leastGrades[start][i]<<" k:"<<k<<" "<<leastGrades[k][count-i]<<endl; int newgrade=leastGrades[start][i]+leastGrades[k][count-i]+countStones; //cout<<"最小成绩:"<<newgrade<<endl; if(newgrade<grade){ grade=newgrade; ks[start][count]=i; } } leastGrades[start][count]=grade; //cout<<"start::"<<start<<"最小值:"<<leastGrades[start][count]<<" c的值:"<<ks[start][count]<<endl; } }
?3.计算所有子问题的最少得分
??/*
计算所有子问题的最少得分 */ void getEachMiniGrades(int *stoneCounts,int **leastGrades,int **ks,int n){ for(int j=2;j<=n;j++){ for(int i=1;i<=n;i++){ getMiniGrade(stoneCounts,leastGrades,ks,i,j,n); } } }
?4.获得最少得分
???/*
获得最少得分 */ int miniGrade(int *stoneCounts,int **leastGrades,int **ks,int n){ getEachMiniGrades(stoneCounts,leastGrades,ks,n); int mini=999999; for(int i=1;i<=n;i++){ int newMini=leastGrades[i][n]; if(newMini<mini){mini=newMini;} } return mini; }
??5.计算子问题的最多得分
???/*
计算从start堆开始,计算count堆石子的最多得分 */ void getMaxGrades(int *stoneCounts,int **maxGrades,int **ks,int start,int count,int n){ if(count>1){ int countStones=0; //计算石子数 for(int j=0;j<count;j++){ countStones=countStones+stoneCounts[start+j]; } int grade=0; for(int i=1;i<count;i++){ int k=(start+i-1)%n+1; int newgrade=maxGrades[start][i]+maxGrades[k][count-i]+countStones; if(newgrade>grade){ grade=newgrade; ks[start][count]=i; } } maxGrades[start][count]=grade; } }
?6.计算每个子问题的最多得分
??/*
计算所有子问题的最多得分 */ void getEachMaxiGrades(int *stoneCounts,int **maxGrades,int **ks,int n){ for(int j=2;j<=n;j++){ for(int i=1;i<=n;i++){ getMaxGrades(stoneCounts,maxGrades,ks,i,j,n); } } }
??7.获得最多得分
??/*
获得最多得分 */ int maxGrade(int *stoneCounts,int **maxGrades,int **ks,int n){ getEachMaxiGrades(stoneCounts,maxGrades,ks,n); int max=0; for(int i=1;i<=n;i++){ int newMax=maxGrades[i][n]; if(newMax>max){max=newMax;} } return max; }
??8.显示每堆石子的数量
???/*
显示各堆石子的数量 */ void showStones(int *stoneCounts,int n){ cout<<"各堆石子数:"<<n<<endl; int count=0; for(int i=1;i<=(n-1);i++){ count++; cout<<stoneCounts[i]<<"\t"; if(count==6){count=0;cout<<endl;} } cout<<endl; }
?9.显示所有子问题的得分情况
???/*
显示各子问题所得分数 */ void showGrades(int **grades,int n){ cout<<"所得分数:"<<endl; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ cout<<grades[i][j]<<"\t"; } cout<<endl; } cout<<endl; }
?10.main函数的内容
?? ??void main(){
FILE *fin,*fout; int n,*stoneCounts,**leastGrades,**maxGrades,**ksMini,**ksMax; if((fin=fopen("input2.txt","r"))==NULL) printf("Read error!!"); fscanf(fin,"%d",&n); fclose(fin); stoneCounts =new int [2*n]; leastGrades = (int **)malloc((n + 1) * sizeof(int *)); maxGrades =(int **)malloc((n + 1) * sizeof(int *)); ksMini =(int **)malloc((n + 1) * sizeof(int *)); ksMax=(int **)malloc((n + 1) * sizeof(int *)); for(int i = 0; i<= n; i++){ leastGrades[i] = (int *)malloc((n + 1) * sizeof(int)); maxGrades[i] = (int *)malloc((n + 1) * sizeof(int)); ksMini[i] = (int *)malloc((n + 1) * sizeof(int)); ksMax[i] = (int *)malloc((n + 1) * sizeof(int)); } init(leastGrades,ksMini,0,n); init(maxGrades,ksMax,1,n); if((fin=fopen("input2.txt","r"))==NULL) printf("Read error!!\n"); fscanf(fin,"%d",&n); for(int j=1;j<=n;j++){ fscanf(fin,"%d",&stoneCounts[j]); } fclose(fin); for(int k=n+1;k<=(2*n-1);k++){ stoneCounts[k]=stoneCounts[k-n]; } showStones(stoneCounts,2*n); int mini=miniGrade(stoneCounts,leastGrades,ksMini,n); cout<<"最少得分的子问题:"<<endl; showGrades(leastGrades,n); int max=maxGrade(stoneCounts,maxGrades,ksMax,n); cout<<"最多得分的子问题:"<<endl; showGrades(maxGrades,n); cout<<"最小值:"<<mini<<"最大值:"<<max<<endl; fout=fopen("output2.txt","w"); fprintf(fout,"%d\n",mini); fprintf(fout,"%d\n",max); fclose(fout); }
??源代码和对石子合并问题的详细解说文档在上上传文件里