当前位置:   article > 正文

石子合并(区间dp)-java_石子合并问题算法思想

石子合并问题算法思想

石子合并问题是经典的区间dp问题,我们需要枚举中间端点k的情况从而来推出dp数组的值。


前言

石子合并问题是经典的区间dp问题,我们需要枚举中间端点k的情况从而来推出dp数组的值。


提示:以下是本篇文章正文内容,下面案例可供参考

一、石子合并问题

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1、2 堆,代价为 9,得到 9 2,再合并得到 11,总代价为 4+9+11=24;

如果第二步是先合并 2、3堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

二、算法思路

1.问题思路

我们引入二维数组dp,dp[i][j]表示的含义是从第i堆,合并到第j堆所需要的最小代价数。

 图1.1思路模拟

 我们可以通过枚举区间长度len,从1枚举n,当然区间长度为1的时候,说明只有一堆石子,就不要合并,代价为0。

然后我们在从1开始枚举左区间的起始端点i,i的值从1枚举到n。我们知道区间长度len,知道左端点i,那么我们就可以得到区间的右端点j=i+len-1。

此时我们再枚举中间端点k,k的值的话从i枚举到j-1,因为我们最后用k将区间分成两堆,分别是i到k堆和k+1到j堆,那么我们k的取值就是从i到j-1。

那么dp[i][j]的值就是从第i堆到第k堆的最小代价数加上第k+1堆到第j堆的最小代价数再加上最后两堆的代价数,我们通过观察可以发现最后两堆的合并一定是第i堆到k堆的连续合并和后面从第k+1堆到第j堆的连续合并之和,那么最后一次就相当于求区间和,我们可以通过前缀和数组的方法来进行处理。

2.状态递推公式

当i=j时,即就一堆:

dp[i][j] = 0

当 枚举中间端点k时:

dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1])

j-1\geqslant k\geqslant i 

注:其中一维数组s表示石子质量的前缀和数组,故求第i堆到第j堆石子重量的和为

       s[j]-s[i-1] 

dp数组构建代码如下:

  1. //区间长度
  2. for(int len = 2; len <= n;len++){
  3. //枚举左端端点
  4. for(int i = 1; i + len - 1 <= n;i++){
  5. //最右端端点
  6. int j = i + len -1;
  7. //初始化
  8. dp[i][j] = Integer.MAX_VALUE;mei
  9. //左右端点相等,那么就一堆,不需要进行操作
  10. if(i == j){
  11. dp[i][j] = 0;
  12. }
  13. //枚举中间端点
  14. for(int k = i; k <= j-1;k++){
  15. dp[i][j] = Math.min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]);
  16. }
  17. }
  18. }

二、代码如下

代码如下(示例):

  1. import java.io.*;
  2. import java.util.ArrayList;
  3. import java.util.Scanner;
  4. public class 石子合并 {
  5. static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
  6. static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  7. static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
  8. public static void main(String[] args) {
  9. Scanner sc = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
  10. int n = sc.nextInt();
  11. int[] s = new int[310];
  12. int[][] dp = new int[310][310];
  13. for(int i = 1; i <= n;i++){
  14. s[i] = sc.nextInt();
  15. }
  16. for(int i = 1;i <= n;i++){
  17. s[i] = s[i]+s[i-1];
  18. }
  19. //区间长度
  20. for(int len = 2; len <= n;len++){
  21. //枚举左端端点
  22. for(int i = 1; i + len - 1 <= n;i++){
  23. //最右端端点
  24. int j = i + len -1;
  25. //初始化
  26. dp[i][j] = Integer.MAX_VALUE;
  27. //左右端点相等,那么就一堆,不需要进行操作
  28. if(i == j){
  29. dp[i][j] = 0;
  30. }
  31. //枚举中间端点
  32. for(int k = i; k <= j-1;k++){
  33. dp[i][j] = Math.min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]);
  34. }
  35. }
  36. }
  37. pw.println(dp[1][n]);
  38. pw.flush();
  39. }
  40. }

2.读入数据

代码如下(示例):

  1. 4
  2. 1 3 5 2

3.代码运行结果如下:

22

总结

石子合并问题是经典的区间dp问题,我们需要枚举中间端点k的情况从而来推出dp数组的值。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/531589
推荐阅读
相关标签
  

闽ICP备14008679号