石子合并问题_C/C++_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > C/C++ > 石子合并问题

石子合并问题

 2010/12/4 11:51:52  茴香豆  http://panlianghui-126-com.javaeye.com  我要评论(0)
  • 摘要:问题描述:在一个园形操场的四周摆放N堆石子(N≤100),现要将石子有次序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。编一程序,由文件读入堆数N及每堆的石子数(≤20),①选择一种合并石子的方案,使得做N-1次合并,得分的总和最小;②选择一种合并石子的方案,使得做N-1次合并,得分的总和最大。例如,所示的4堆石子,每堆石子数(从最上面的一堆数起,顺时针数)依次为4594。则3次合并得分总和最小的方案:8+13+22=43得分最大的方案为
  • 标签:问题

问题描述:

??在一个园形操场的四周摆放N堆石子(N≤100),现要将石子有次序地合并成一堆。规定

????? 每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
?????
编一程序,由文件读入堆数N及每堆的石子数(20),
????? ①
选择一种合并石子的方案,使得做N1次合并,得分的总和最小;
????? ②
选择一种合并石子的方案,使得做N1次合并,得分的总和最大。
?????
例如,所示的4堆石子,每堆石子数(从最上面的一堆数起,顺时针数)依
?????
次为4594。则3次合并得分总和最小的方案:8+13+22=43
?????
得分最大的方案为:14+18+22=54

该问题应该用动态规划算法才能得到最优解,而贪心算法得不到最优解。

而动态规划算法的基本要素是:最优子结构和重叠子问题


针对该问题,我们用f(i,j)表示从第i堆石子数起,顺时针数j堆石子的得分,则它的动态规划方程为

?当求最大得分总和时

????? fij〕=maxfik〕+fxj-k〕+t
?????
≤k≤j-1
????? c
ij〕=k│ fij〕=fik〕+fxj-k〕+t
?????
(2n,1n)

?????
当求最小得分总和时
????? f
ij〕=minfik〕+fxjk〕+t
?????
≤k≤j-1


????? c
ij〕=k│ fij〕=fik〕+fxj-k〕+t
?????
(2n,1n)
?????
其中x=(ik-1)modn+1,即第i堆数起,顺时针数k+1堆的堆序号。


?? ? 对每一堆石子来说,它的
????? f
i,1〕=0
????? c
i,1〕=0 (1≤i≤N
?????
对于子序列〔ij〕来说,若求最小得分总和,fij〕的初始值为; 若求最大得分总和,fij〕的初始值为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 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???? ->
?????
第四次合并 ……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);
}



??源代码和对石子合并问题的详细解说文档在上上传文件里

发表评论
用户名: 匿名