区间DP


一、前言

最近写的一些相关文章,主要会整理一下相关题目,此系列会不断的更新,只要遇到了相关的题目就都会整理在一起!本篇主要针对区间DP进行相关整理。区间dp有一个特点,就是一般题目都会对一个区间进行一些处理,而此dp的状态定义也与区间相关。而整个题目的分析,采取闫式dp分析法,从状态表示,状态计算两个方面分析每一个dp题目。希望之后做到类似的题目的时候能够更快速的分析出如何解题!

本章题大多来源于ACwing,欢迎大家来这个平台一起学习算法!!

题目汇总

①ACwing 282.石子合并

石子合并

注意:

本题和大家比较常见的合并果子那一个贪心题不同,因为此题必须合并相邻的两堆石子。如果合并的顺序是任意的话才能够利用贪心!

DP分析

此处解释上方的一个分析方法

对于动态规划来说,如何能够正确的写出这类的题目,首先要知道,先定义一个与题目相关的状态表示,而这样的状态表示恰好表示一类集合,而这一类集合表达出来以后,是可以先考虑以集合的最后一个点作为分界点往前看。

假设我们在计算f(i, j)的时候,我们可以以j为分界点,把集合分成j - 1的子集合,状态转移中的m分界点。注意这个m所选取的区间是落在[i, j)这个区间内的。

为什么i为闭区间,j为开区间呢?

因为选i为分界点,可以分为拿第一个和后面所有为两个区间

而如果选j为分界点,就无法分成两堆了!

一旦分好这些分界点之后,就知道如何进行递推了。

状态转移

如果以m为分界点,我们要计算f(i, j)的值

$f(i, j) = min(f(i, m) + f(m + 1, j) + sum(i, j)), m \in [i, j)$

其中sum(i, j)表示[i, j]区间所有果子合并需要花费的体力值,这个也就是合并两段区间所需要的体力值!而因为这个是一个区间求和的问题,因此再计算sum的时候就可以利用前缀和进行一个优化了!

最后结果就是:f(1,n)

初始化

对于初始化来说,有下面情况

i == j : 就是合并一个石子的最小花费为0(因为不需要合并)

i < j : 最小花费初始化为$+\infty$,因为要求最小值,所以可以初始成一个很大的数,这里不能说初始化为0,因为0代表无花费,这样的话怎么推导,都是最小为0,那么最后的答案肯定是0

完整AC代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 305, INF = 0x3f3f3f3f;
//初始化1
int f[N][N];
//前缀和数组
int sum[N];

int main() {
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++ ){
        cin >> sum[i];
        sum[i] += sum[i - 1];
    }
    
    //先枚举可能的区间长度
    for(int len = 2; len <= n; len ++ ) {
        //左端点为i,右端点为i + len - 1;
        //满足右端点在n内就可以满足左端点范围
        for(int i = 1; i + len - 1 <= n; i ++ ) {
            int l = i, r = i + len - 1;
            //初始化2
            f[l][r] = INF;
            
            //枚举分界点m
            for(int m = l; m < r; m ++ ) {
                //状态转移
                f[l][r] = min(f[l][r], f[l][m] + f[m + 1][r] + sum[r] - sum[l - 1]);
            }
        }
    }
    
    cout << f[1][n] << endl;
    
    return 0;
}

文章作者: sheepice
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 sheepice !
  目录