`

石子合并问题

 
阅读更多

[问题描述]:
  设有n堆石子排成一排,其编号为1、2、3、…、n(n<=100)。每堆石子的数量用:a[1]、a[2]、…、a[n] 表示,现将这n堆石子归并成一堆,归并的规则:

◆每次只能将相邻两堆归并成一堆,即:第 1 堆石子 a[1] 只能与第 2 堆石子 a[2] 归并,最后一堆石子 a[n] 只能与 a[n-1] 归并,中间的石子 a[i] 只能与 a[i-1] 或 a[i+1] 归并;

◆每次归并的代价是两堆石子的重量之和。
  我们假如5堆的石子,其中石子数分别为7,6,5,7,100

      •按照贪心法,合并的过程如下:
        每次合并得分
        第一次合并  7  6   5   7    100   =11
      第二次合并  7   11     7   100=18
      第三次合并  18    7    100 =25
        第四次合并   25   100 =125

        总得分=11+18+25+125=179

       •另一种合并方案

        每次合并得分
       第一次合并  7  6   5   7    100   ->13
         第二次合并  13   5     7   100->12
         第三次合并  13    12    100 ->25
         第四次合并   25   100 ->125

         总得分=13+12+25+125=175

         显然利用贪心来做是错误的,贪心算法在子过程中得出的解只是局部最优,而不能保证使得全局的值最优。

    

         如果N-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的。

     因此我们需要通过动态规划算法来求出最优解。

 

一:任意版
有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为的一堆石子的数量。设计一个算法,将这N堆石子合并成一堆的总花费最小(或最大)。
此类问题比较简单,就是哈夫曼编码的变形,用贪心算法即可求得最优解。即每次选两堆最少的,合并成新的一堆,直到只剩一堆为止。证明过程可以参考哈夫曼的证明过程。
 所用的数据结构:
1、 是堆,取两次堆顶的最小元素,相加后再加入堆中,重复n-1次即可。
2、 两个队列,一个是原始的从小到大排序后的石子序列A。
        一个合并后的石子生成的序列B,
  注意:这两个序列都是有序的(从小到大),总是从它们中取出最小的两个相加到序列B。

二:直线版
在一条直线上摆着N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为将的一堆石子的数量。设计一个算法,将这N堆石子合并成一堆的总花费最小(或最大)。
如果熟悉矩阵连乘对这类问题肯定非常了解。矩阵连乘每次也是合并相邻两个矩阵(只是计算方式不同)。那么石子合并问题可用矩阵连乘的方法来解决。
那么最优子结构是什么呢?如果有N堆,第一次操作肯定是从n-1个对中选取一对进行合并,第二次从n-2对中选取一对进行合并,以此类推……

分析:我们熟悉矩阵连乘,知道矩阵连乘也是每次合并相邻的两个矩阵,那么石子合并可以用矩阵连乘的方式来解决。

设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:
dp[i, j] = 0; (i=j)
dp[i, j] = min{ dp[i, k] + dp[k+1, j] } + sum[i, j]; (i != j)

public int minCost(int[] A) {
	int n = A.length;
	int[][] f = new int[n][n];
	int[] sum = new int[n];
	for(int i=0; i<n; i++) {
		sum[i] = (i>0?sum[i-1]:0) + A[i];
	}
	for(int i=0; i<n; i++) {
		Arrays.fill(f[i], Integer.MAX_VALUE);
		f[i][i] = 0;
	}
	for(int w=2; w<=n; w++) {		// sliding window size
		for(int i=0; i<=n-w; i++) { // window start position
			int j = i+w-1;			// window end position
			int sumij = sum[j] - (i>0?sum[i-1]:0);
			for(int k=i; k<j; k++) {
				f[i][j] = Math.min(f[i][j], f[i][k] + f[k+1][j] + sumij);
			}
		}
	}
	return f[0][n-1];
}

 
三:圆形版
如果石子是排成圆形,其余条件不变,那么最优值又是什么呢?
因为圆形是首尾相接的,初一想,似乎与直线排列完全成了两个不同的问题。因为每次合并后我们都要考虑最后一个与第一个的合并关系。直线版的矩阵连乘对角线式的最优子结构不见了。f(i, j)表示i-j合并的最优值似乎并不可行,因为我们可以得到的最优值第一步就是第一个与最后一个合并,那么f(i, j)并不能表示这种关系。
修改一下,f(i, j)表示从第i个开始,合并后面j个得到的最优值。sum(i, j)表示从第i个开始直到i+j个的数量和。那么这个问题就得到解决了。注意要把其看成环形,即在有限域内的合并。

破圆化直:将圆形的石子归并化为直线型石子归并。
方法是:将原来的石子长度增加一倍,加在原来的后面,a[1]~a[n],a[1]~a[n],
求从1,2,3,~n开始的n个合并的最小值,最其中一个最小值即可。

状态转移方程为:
其中有:

#
上面第二类与第三类的代码复杂度都是O(n^3),n为石子堆数目,那么还有没有复杂度更低的方法呢?有的。也是使用动态规划,由于过程满足平行四边形法则,优化后可以将复杂度降为O(n^2)。

 

Reference: 

http://jsezzxl.wicp.net/Problem_Show.asp?id=1221

http://blog.csdn.net/acdreamers/article/details/18039073

http://blog.csdn.net/acdreamers/article/details/18043897

分享到:
评论

相关推荐

    石子合并问题的 动态规划解法

    //--石子合并问题 /*问题描述:在一个圆形操场的四周摆放着n堆石子,先要将石子有序的合并为一堆。规定每次只能选相邻的石子合并成一堆,并将新一堆的石子数 记录为该次合并的得分。试设计一个算法,记录n堆石子...

    算法设计与分析:石子合并问题

    《算法设计与分析:石子合并问题》 石子合并问题是一个典型的动态规划问题,它源于实际生活中的操作,如游戏或资源优化等场景。在这个问题中,我们面临一个圆形操场上的n堆石子,目标是通过相邻两堆石子的合并,...

    【算法设计分析课程设计】动态规划解决石子合并问题及回溯法解决运动员匹配问题

    针对石子合并问题,本文利用动态规划算法寻求石子合并时的最大,最小得分,选择相邻的两堆石子堆进行合并,其最终花费的代价与石子堆的排列顺序有关。根据其重叠子问题建立状态转移方程,利用程序进行求解。算例结果...

    动态规划 解决石子合并问题

    动态规划 解决石子合并问题 在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。规定每次只 能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出...

    动态规划解石子合并问题

    在这个“石子合并问题”中,我们面临的就是这样一个挑战。问题描述可能涉及在环形排列的石子上进行一系列合并操作,每次操作可以将相邻的两堆石子合并成一堆,消耗一定的能量。目标可能是最小化完成所有合并操作所需...

    石子合并 问题 动态规划 源码

    用 动态规划 解决 石子合并 问题 用 动态规划 解决 石子合并 问题 用 动态规划 解决 石子合并 问题

    石子合并问题代码

    算法分析课程作业,C语言编写,可能需要用input.txt输入,石子合并问题代码

    石子合并问题可运行Java源码

    石子合并问题可运行Java源码,下载到Eclipse就可以运行了。 要求在工程根目录下有input.txt文件,结果output.txt会生成在相同目录下

    实现3-3石子合并问题.cpp

    实现3-3石子合并问题.cpp

    石子合并问题 用动态规划方法

    ### 石子合并问题与动态规划 #### 一、问题背景及定义 在计算机科学领域,特别是算法设计中,石子合并问题是常见的一个问题。该问题通常可以这样描述:假设有一行排布的N个石子,每个石子有一个特定的分数。玩家的...

    石子合并 在一个圆形操场的四周摆放着 n 堆石子. 现要将石子有次序地合并成一堆, 规定每次只能选相邻的 2 堆石子合并成新的一堆, 并将新的一堆石子数记为该次合并的得分.

    ### 石子合并问题解析与算法实现 #### 知识点概述 石子合并问题是在计算机科学领域中一种经典的动态规划问题,它涉及到数组、循环、动态规划算法以及位运算等核心概念。在这个问题中,我们需要在一个圆形操场的...

    C语言实现的石子合并问题

    C语言实现的石子合并问题,自己可以改成其他的语言,这是核心语句

    石子合并问题JAVA源代码

    ### 石子合并问题分析与Java实现 #### 一、问题背景及定义 在一个圆形操场的四周摆放着\( n \)堆石子。任务是将这些石子有序地合并成一堆。每次只能选择两堆相邻的石子进行合并,新合并的一堆石子数为这两堆石子数...

    1137: 石子合并问题

    规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。 编程任务: 对于给定n堆石子,编程计算合并成一堆的最小...

    石子合并问题共1页.pdf.zip

    标题中的“石子合并问题”通常是指一个计算机科学或算法设计中的经典问题,它与数据结构和动态规划等基础知识紧密相关。在这个问题中,我们通常想象有若干堆石子,每堆有不同的数量,两个玩家轮流从任一堆中取走一定...

    算法设计——石子合并问题

    ### 算法设计——石子合并问题 #### 背景介绍 在算法设计领域,特别是针对优化问题的研究中,“石子合并问题”是一种典型的动态规划应用案例。该问题通常表述为:在一个直线上放置若干堆石子,每堆石子的数量已知。...

Global site tag (gtag.js) - Google Analytics