区间动态规划精解

区间动态规划概述

区间动态规划(Interval Dynamic Programming)是动态规划的一种特殊形式,主要用于解决涉及区间或子序列的问题。这类问题通常需要计算某个区间的最优解,并通过合并子区间的最优解来构造更大区间的最优解。常见应用包括矩阵链乘法、石子合并、最长回文子序列等。

区间动态规划的基本思想

区间动态规划的核心思想是将问题分解为若干子区间,通过求解子区间的最优解来逐步构建更大区间的最优解。其基本步骤如下:

  1. 定义状态 :通常使用二维数组 dp[i][j] 表示区间 [i, j] 的最优解。
  2. 初始化状态:对长度为1的区间进行初始化。
  3. 状态转移方程:根据问题特点,设计如何通过子区间的最优解合并得到更大区间的最优解。
  4. 计算顺序:按照区间长度从小到大进行计算。

区间动态规划的经典问题

石子合并问题

问题描述:有 n 堆石子排成一列,每次只能合并相邻的两堆石子,合并的代价为两堆石子的数量之和。求将所有石子合并成一堆的最小总代价。

状态定义
dp[i][j] 表示合并第 i 堆到第 j 堆石子的最小代价。

初始化

对于长度为1的区间,合并代价为0,即 dp[i][i] = 0

状态转移方程

对于区间 [i, j],枚举分割点 ki ≤ k < j),将区间分为 [i, k][k+1, j],合并代价为 dp[i][k] + dp[k+1][j] + sum[i][j],其中 sum[i][j] 是区间 [i, j] 的石子总数。

cpp 复制代码
for (int len = 2; len <= n; ++len) {
    for (int i = 1; i + len - 1 <= n; ++i) {
        int j = i + len - 1;
        dp[i][j] = INF;
        for (int k = i; k < j; ++k) {
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);
        }
    }
}
矩阵链乘法

问题描述:给定一系列矩阵,计算它们相乘的最小乘法次数。矩阵乘法的次数由矩阵的维度决定。

状态定义
dp[i][j] 表示计算第 i 个到第 j 个矩阵相乘的最小乘法次数。

初始化

对于单个矩阵,乘法次数为0,即 dp[i][i] = 0

状态转移方程

对于区间 [i, j],枚举分割点 ki ≤ k < j),将问题分解为计算 [i, k][k+1, j] 的最小乘法次数,再加上合并这两个子问题的代价。

cpp 复制代码
for (int len = 2; len <= n; ++len) {
    for (int i = 1; i + len - 1 <= n; ++i) {
        int j = i + len - 1;
        dp[i][j] = INF;
        for (int k = i; k < j; ++k) {
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + p[i-1] * p[k] * p[j]);
        }
    }
}

区间动态规划的优化

在某些情况下,区间动态规划可以通过优化技术(如四边形不等式)降低时间复杂度。例如,石子合并问题的时间复杂度可以从 O(n\^3) 优化到 O(n\^2)

区间动态规划的代码实现

以下是一个完整的石子合并问题的C++实现:

cpp 复制代码
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> stones(n + 1, 0);
    vector<int> prefix_sum(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cin >> stones[i];
        prefix_sum[i] = prefix_sum[i - 1] + stones[i];
    }

    vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
    for (int len = 2; len <= n; ++len) {
        for (int i = 1; i + len - 1 <= n; ++i) {
            int j = i + len - 1;
            dp[i][j] = INT_MAX;
            for (int k = i; k < j; ++k) {
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + prefix_sum[j] - prefix_sum[i - 1]);
            }
        }
    }

    cout << dp[1][n] << endl;
    return 0;
}

区间动态规划的常见问题

  1. 边界条件:初始化时需注意区间长度为1的情况。
  2. 计算顺序:必须按照区间长度从小到大计算。
  3. 时间复杂度:未经优化的区间动态规划通常为 O(n\^3),需注意数据规模。

区间动态规划的扩展

区间动态规划可以与其他算法结合,例如记忆化搜索、单调队列优化等。以下是一个使用记忆化搜索实现的区间动态规划示例:

cpp 复制代码
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

vector<vector<int>> dp;
vector<int> prefix_sum;

int solve(int i, int j) {
    if (i == j) return 0;
    if (dp[i][j] != -1) return dp[i][j];
    int res = INT_MAX;
    for (int k = i; k < j; ++k) {
        res = min(res, solve(i, k) + solve(k + 1, j) + prefix_sum[j] - prefix_sum[i - 1]);
    }
    return dp[i][j] = res;
}

int main() {
    int n;
    cin >> n;
    prefix_sum.resize(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cin >> prefix_sum[i];
        prefix_sum[i] += prefix_sum[i - 1];
    }
    dp.assign(n + 1, vector<int>(n + 1, -1));
    cout << solve(1, n) << endl;
    return 0;
}

区间动态规划的注意事项

  1. 状态定义 :明确 dp[i][j] 的具体含义。
  2. 初始化:确保所有必要的初始状态已正确设置。
  3. 状态转移:确保转移方程覆盖所有可能的分割点。
  4. 计算顺序:避免重复计算或遗漏某些状态。

区间动态规划的练习题

  1. 石子合并:计算合并相邻石子的最小代价。
  2. 矩阵链乘法:计算矩阵相乘的最小乘法次数。
  3. 最长回文子序列:寻找给定字符串的最长回文子序列。
  4. 括号匹配:计算最长的合法括号子序列。

区间动态规划的总结

区间动态规划是一种高效解决区间或子序列问题的算法。通过将问题分解为子区间并逐步合并,能够有效求解复杂的最优化问题。掌握区间动态规划的关键在于理解状态定义、转移方程和计算顺序。通过大量练习,可以熟练应用区间动态规划解决实际问题。

相关推荐
我叫Ycg2 小时前
C++ 中关于插入函数insert() 与 emplace() 的区别与使用建议
开发语言·c++
Q741_1472 小时前
每日一题 力扣 3761. 镜像对之间最小绝对距离 哈希表 数组 C++ 题解
c++·算法·leetcode·哈希算法·散列表
John.Lewis2 小时前
C++加餐课-哈希:扩展学习(2)布隆过滤器
c++·算法·哈希算法
网域小星球2 小时前
C++ 从 0 入门(三)|类与对象基础(封装、构造 / 析构函数,面试必考)
开发语言·c++·面试·构造函数·析构函数
j_xxx404_2 小时前
Linux:缓冲区
linux·运维·c++·后端
我真不是小鱼2 小时前
cpp刷题打卡记录29——矩阵置零 & 旋转图像 & 除了自身以外数组的乘积
数据结构·c++·算法·leetcode·矩阵
网域小星球2 小时前
C++ 从 0 入门(二)|引用与指针区别、函数重载、内联函数(面试高频)
开发语言·c++·面试·函数重载·内联函数·引用与指针区别
代码中介商2 小时前
C++ 多态与虚函数入门:从概念到规则
开发语言·c++
kyle~2 小时前
工业以太网协议---EtherCAT
开发语言·c++·网络协议·机器人·ros2