区间动态规划例题:石子合并问题
字数 1670 2025-10-25 15:28:46

区间动态规划例题:石子合并问题

题目描述
有N堆石子排成一排,每堆石子有一定的数量。现要将所有石子合并成一堆。合并的规则是:每次只能选择相邻的两堆石子合并成一堆,合并的代价是这两堆石子的数量之和。请设计一个算法,计算出将N堆石子合并成一堆的最小总代价。

例如:有4堆石子,数量依次为4, 5, 9, 4。
一种合并方案及其总代价计算如下:

  1. 合并前两堆 (4, 5),代价 4+5=9,得到新石子堆序列 [9, 9, 4](总代价=9)
  2. 合并后两堆 (9, 4),代价 9+4=13,得到新序列 [9, 13](总代价=9+13=22)
  3. 合并最后两堆 (9, 13),代价 9+13=22,得到一堆 [22](总代价=22+22=44)
    总代价为44。但这是最优解吗?我们需要找到最小总代价。

解题过程

1. 问题分析
这是一个典型的区间动态规划问题。关键特征:

  • 操作对象是线性排列的多个元素(石子堆)
  • 每次操作针对相邻区间,合并后产生新元素
  • 需要找到最优的操作顺序以最小化总代价

2. 定义状态
定义dp[i][j]表示将第i堆到第j堆石子合并成一堆的最小代价(1 ≤ i ≤ j ≤ N)。
最终目标是求dp[1][N]

3. 状态转移方程
考虑最后一次合并:当合并[i,j]区间时,最后一步一定是将[i,k]和[k+1,j]两个区间合并(i ≤ k < j)。
因此:dp[i][j] = min(dp[i][k] + dp[k+1][j] + sum(i,j)),其中sum(i,j)是i到j堆石子的总数量(合并[i,k]和[k+1,j]的代价)。

4. 预处理前缀和
为了快速计算sum(i,j),预处理前缀和数组prefix:
prefix[0] = 0, prefix[i] = stones[1] + stones[2] + ... + stones[i]
那么sum(i,j) = prefix[j] - prefix[i-1]

5. 计算顺序
由于大区间依赖小区间,需要按区间长度从小到大计算:

  • 先计算所有长度为1的区间(dp[i][i] = 0,单堆无需合并)
  • 再计算长度为2, 3, ..., N的区间

6. 详细计算示例(石子[4,5,9,4])
前缀和:prefix = [0,4,9,18,22](索引0~4)

长度=2

  • dp[1][2] = dp[1][1]+dp[2][2]+sum(1,2) = 0+0+(9-0)=9
  • dp[2][3] = 0+0+(18-4)=14
  • dp[3][4] = 0+0+(22-9)=13

长度=3

  • dp[1][3] = min(
    k=1: dp[1][1]+dp[2][3]+sum(1,3) = 0+14+(18-0)=32,
    k=2: dp[1][2]+dp[3][3]+sum(1,3) = 9+0+18=27
    ) = 27
  • dp[2][4] = min(
    k=2: dp[2][2]+dp[3][4]+sum(2,4) = 0+13+(22-4)=31,
    k=3: dp[2][3]+dp[4][4]+sum(2,4) = 14+0+18=32
    ) = 31

长度=4
dp[1][4] = min(
k=1: dp[1][1]+dp[2][4]+sum(1,4) = 0+31+22=53,
k=2: dp[1][2]+dp[3][4]+sum(1,4) = 9+13+22=44,
k=3: dp[1][3]+dp[4][4]+sum(1,4) = 27+0+22=49
) = 44

7. 算法实现

def stone_merge(stones):
    n = len(stones)
    prefix = [0]*(n+1)
    for i in range(1, n+1):
        prefix[i] = prefix[i-1] + stones[i-1]
    
    dp = [[0]*n for _ in range(n)]
    
    for length in range(2, n+1):  # 区间长度
        for i in range(n-length+1):  # 区间起点
            j = i+length-1  # 区间终点
            dp[i][j] = float('inf')
            for k in range(i, j):  # 分割点
                cost = dp[i][k] + dp[k+1][j] + prefix[j+1]-prefix[i]
                if cost < dp[i][j]:
                    dp[i][j] = cost
    
    return dp[0][n-1]

8. 复杂度分析
时间复杂度:O(N³)(三层循环)
空间复杂度:O(N²)(DP表)

这个解法通过系统性地计算所有可能区间的最优解,保证了找到全局最优解。

区间动态规划例题:石子合并问题 题目描述 有N堆石子排成一排,每堆石子有一定的数量。现要将所有石子合并成一堆。合并的规则是:每次只能选择相邻的两堆石子合并成一堆,合并的代价是这两堆石子的数量之和。请设计一个算法,计算出将N堆石子合并成一堆的最小总代价。 例如:有4堆石子,数量依次为4, 5, 9, 4。 一种合并方案及其总代价计算如下: 合并前两堆 (4, 5),代价 4+5=9,得到新石子堆序列 [ 9, 9, 4 ](总代价=9) 合并后两堆 (9, 4),代价 9+4=13,得到新序列 [ 9, 13 ](总代价=9+13=22) 合并最后两堆 (9, 13),代价 9+13=22,得到一堆 [ 22 ](总代价=22+22=44) 总代价为44。但这是最优解吗?我们需要找到最小总代价。 解题过程 1. 问题分析 这是一个典型的区间动态规划问题。关键特征: 操作对象是线性排列的多个元素(石子堆) 每次操作针对相邻区间,合并后产生新元素 需要找到最优的操作顺序以最小化总代价 2. 定义状态 定义 dp[i][j] 表示将第i堆到第j堆石子合并成一堆的最小代价(1 ≤ i ≤ j ≤ N)。 最终目标是求 dp[1][N] 。 3. 状态转移方程 考虑最后一次合并:当合并[ i,j]区间时,最后一步一定是将[ i,k]和[ k+1,j]两个区间合并(i ≤ k < j)。 因此: dp[i][j] = min(dp[i][k] + dp[k+1][j] + sum(i,j)) ,其中sum(i,j)是i到j堆石子的总数量(合并[ i,k]和[ k+1,j ]的代价)。 4. 预处理前缀和 为了快速计算sum(i,j),预处理前缀和数组prefix: prefix[0] = 0 , prefix[i] = stones[1] + stones[2] + ... + stones[i] 那么 sum(i,j) = prefix[j] - prefix[i-1] 5. 计算顺序 由于大区间依赖小区间,需要按区间长度从小到大计算: 先计算所有长度为1的区间(dp[ i][ i ] = 0,单堆无需合并) 再计算长度为2, 3, ..., N的区间 6. 详细计算示例(石子[ 4,5,9,4]) 前缀和:prefix = [ 0,4,9,18,22 ](索引0~4) 长度=2 : dp[ 1][ 2] = dp[ 1][ 1]+dp[ 2][ 2 ]+sum(1,2) = 0+0+(9-0)=9 dp[ 2][ 3 ] = 0+0+(18-4)=14 dp[ 3][ 4 ] = 0+0+(22-9)=13 长度=3 : dp[ 1][ 3 ] = min( k=1: dp[ 1][ 1]+dp[ 2][ 3 ]+sum(1,3) = 0+14+(18-0)=32, k=2: dp[ 1][ 2]+dp[ 3][ 3 ]+sum(1,3) = 9+0+18=27 ) = 27 dp[ 2][ 4 ] = min( k=2: dp[ 2][ 2]+dp[ 3][ 4 ]+sum(2,4) = 0+13+(22-4)=31, k=3: dp[ 2][ 3]+dp[ 4][ 4 ]+sum(2,4) = 14+0+18=32 ) = 31 长度=4 : dp[ 1][ 4 ] = min( k=1: dp[ 1][ 1]+dp[ 2][ 4 ]+sum(1,4) = 0+31+22=53, k=2: dp[ 1][ 2]+dp[ 3][ 4 ]+sum(1,4) = 9+13+22=44, k=3: dp[ 1][ 3]+dp[ 4][ 4 ]+sum(1,4) = 27+0+22=49 ) = 44 7. 算法实现 8. 复杂度分析 时间复杂度:O(N³)(三层循环) 空间复杂度:O(N²)(DP表) 这个解法通过系统性地计算所有可能区间的最优解,保证了找到全局最优解。