区间DP石子合并

文章讲述了如何使用区间dp方法处理石子合并问题,涉及状态转移、规模定义及代码实现。
摘要由CSDN通过智能技术生成

前言

区间dp也符合动态规划的三阶段,但是也有自己特殊的地方,先来看一下总结,然后再在后面的题目中体验。

第一阶段定义dp数组
(1)缩小规模。在区间dp里,规模是区间的大小。当区间规模大小固定时,而不同的左右端点所产生的区间又是不一样的,所以需要两个维度来表示区间的左右端点。

(2)考虑限制。

(3)写出dp数组。
这里的转移要注意,一般背包DP,线性DP,对于dp[i]是从dp[i-1]转移的。
而区间DP,对于 d p [ l ] [ r ] dp[l][r] dp[l][r]大多数是从 d p [ l + 1 ] [ r ] dp[l+1][r] dp[l+1][r] d p [ l ] [ r − 1 ] dp[l][r-1] dp[l][r1]转移,考虑的时候可以优先考虑这样转移。

(4)修改dp数组。

第二阶段推导状态转移方程

第三阶段写代码

第一层for循环遍历规模,在区间dp里面就是遍历区间长度
第二层for循环遍历区间,遍历的是区间的左端点,根据区间长度和左端点,区间右端点就出来了。
第三层for循环遍历转移点,对于一个区间[l,r]来说,它可以通过[l,k]和[k+1,r]合成得到,第三层遍历的就是区间断点k。

石子合并

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

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

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

例如有 44 堆石子分别为 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。

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

输入格式

第一行一个数 N 表示石子的堆数 N。

第二行 N个数,表示每堆石子的质量(均不超过 1000)。

输出格式

输出一个整数,表示最小代价。

数据范围

1≤N≤300

输入样例

4
1 3 5 2

输出样例

22
题目分析

动态规划三阶段

第一个阶段定义dp数组

(1)缩小规模。我需要考虑的石子堆数为n,每次合并石子石子的堆数都会减少,那么我的规模就是当前合并的石子堆数。如果我要合并两堆石子,既可以是[2,3]也可以是[3,4],也就是这两堆石子的左右端点我要确定一下,那么定义 d p [ l ] [ r ] dp[l][r] dp[l][r]表示合并区间[l,r]里的石子为一堆。

(2)考虑限制。没有限制。

(3)定义dp数组。 d p [ l ] [ r ] dp[l][r] dp[l][r]表示合并区间[l,r]里的石子为一堆所花费的最小代价。

第二个阶段推导状态转移方程

对于区间合并,我们需要枚举区间的断点k,合并式子如下

d p [ l ] [ r ] = d p [ l ] [ k ] + d p [ k + 1 ] [ r ] + w [ l ] [ r ] + w [ k ] [ r ] dp[l][r]=dp[l][k]+dp[k+1][r]+w[l][r]+w[k][r] dp[l][r]=dp[l][k]+dp[k+1][r]+w[l][r]+w[k][r]

这里的 w [ l ] [ k ] w[l][k] w[l][k] w [ k ] [ r ] w[k][r] w[k][r]分别表示区间[l,k]石子的总质量和区间[k,r]石子的总质量。他要怎么求呢?希望能用O(1)的时间复杂度求,那么其实就是一个区间和,可以利用前缀和数组求解。区间[l,k]石子的总质量和区间[k,r]石子的总质量就是区间[l,r]石子的总重量。

d p [ l ] [ r ] = d p [ l ] [ k ] + d p [ k + 1 ] [ r ] + s u m [ r ] − s u m [ l − 1 ] dp[l][r]=dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1] dp[l][r]=dp[l][k]+dp[k+1][r]+sum[r]sum[l1]

第三个阶段写代码

(1)dp数组的初始化。最初的状态初始值需要初始化,最初的状态是一堆石子都没合并,也就是 d p [ i ] [ i ] = 0 dp[i][i]=0 dp[i][i]=0。其余的均初始化为最大值。

(2)递推dp数组。

a.第一层for循环表示的是区间长度 len表示当前合并的石子堆数 2:n

b.第二层for循环表示的是区间左端点 i表示区间左端点 1:n

区间右端点j=i+len-1

c.第三层for循环表示的是dp数组的转移点 k表示的是转移点 l:r-1

(3)表示答案。 d p [ 1 ] [ n ] dp[1][n] dp[1][n]

题目代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int[] a = new int[n+1];
    long[] sum = new long[n+1];
    long[][] dp = new long[n+1][n+1];
    for (int i = 0; i < dp.length; i++) {
        Arrays.fill(dp[i], Integer.MAX_VALUE);
    }
    for (int i = 1; i < n + 1; i++) {
        a[i] = scanner.nextInt();
        sum[i] = sum[i-1] + a[i];//预处理出来前缀和数组
        dp[i][i] = 0;//dp数组初始化
    }
    for (int len = 2; len <= n; len++) {
        for (int i = 1; i+len-1 < n+1; i++) {
            int j = i + len-1;
            for (int k = i; k < j; k++) {
                dp[i][j] = Math.min(dp[i][j], dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
            }
        }
    }
    System.out.println(dp[1][n]);
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值