区间DP——AcWing 320. 能量项链

区间DP

定义

区间动态规划(Interval Dynamic Programming),简称区间DP,是动态规划领域的一个重要分支,专门用于解决涉及区间问题的最优化问题。这类问题通常需要在给定的一组区间上找到最优解,比如求解最长上升子序列、最优三角剖分、区间覆盖问题等。

基本概念

  1. 区间定义:问题中涉及到的区间通常由两个端点(起点和终点)定义,如[i, j]表示一个闭区间。
  2. 状态表示:区间DP的核心在于状态的定义,状态通常由区间长度和区间位置来描述,例如dp[i][j]可以表示区间[i, j]上的最优解。
  3. 状态转移:区间DP的状态转移是从较小区间的信息推导出较大区间的信息。通常通过枚举区间长度或枚举区间断点来进行状态转移。
  4. 决策过程:在区间DP中,一个状态的值可能由多个子区间状态经过某种计算(如最大值、最小值、和等)得到,这是通过决策过程来确定的。

运用情况

通常用于具有区间合并、分割等特征的问题。比如计算一段区间的最优值(如最大和、最小和等),或者判断区间内的某种状态。一些常见的应用场景包括计算字符串的编辑距离、计算区间内的最大连续子段和等。

注意事项

  • 正确定义状态,清晰表示出区间的特征和所需的信息。
  • 仔细考虑区间的划分和合并方式,确保覆盖所有情况且不重复计算。
  • 注意边界条件的处理。

解题思路

  • 确定区间的表示方式,通常用左右端点来表示一个区间。
  • 设计状态表示,比如用 dp[i][j] 表示区间[i,j]的某种最优值或状态。
  • 写出状态转移方程,根据问题的具体要求,确定如何从较小的区间的状态推导出较大区间的状态。
  • 按照合适的顺序进行计算,通常是从小到大逐步计算出各个区间的状态。

例如,计算一个数列在某区间内的最大连续子段和问题。可以定义 dp[i][j] 为区间[i,j]内的最大连续子段和,然后通过考虑区间的分割情况来推导出状态转移方程。

解题步骤

  1. 定义状态:明确dp数组的含义,比如dp[i][j]表示什么。
  2. 初始化:确定dp数组的起始值,通常是当区间长度为1或2时的初始情况。
  3. 状态转移方程:根据问题特性,推导出如何从较小的子区间状态计算出较大区间状态的公式。
  4. 遍历顺序:通常按照区间长度从小到大,然后是区间起始点的顺序进行遍历,确保计算每个状态时,其依赖的所有子状态已经计算完毕。
  5. 求解目标:根据问题的具体要求,从dp数组中提取出最终答案。

实例应用

  • 最长公共子序列(LCS)问题:可以转化为区间DP问题,求解两个序列的最长公共子序列长度。
  • 最优三角剖分:在平面上有n个点,每个点的坐标为(xi, yi),找到一个三角剖分,使得所有三角形的面积之和最大。
  • 区间覆盖问题:给定一系列区间,找到最少数量的区间,使得它们覆盖整个数轴。

区间DP的难点在于正确定义状态和设计高效的状态转移方程,以及理解区间如何相互作用以达到全局最优解。掌握区间DP的关键在于多练习,理解典型问题的解决方案,并能够抽象出问题的共通模式。

AcWing 320. 能量项链

题目描述

320. 能量项链 - AcWing题库

运行代码

cpp 复制代码
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
int n;
int w[N];
int f[N][N];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> w[i];
        w[i + n] = w[i];
    }
    for (int len = 2; len <= n + 1; len ++ )
        for (int l = 1; l + len - 1 <= n * 2; l ++ )
        {
            int r = l + len - 1;
            for (int k = l + 1; k < r; k ++ )
                f[l][r] = max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);
        }
    int res = 0;
    for (int l = 1; l <= n; l ++ ) res = max(res, f[l][l + n]);
    cout << res << endl;
    return 0;
}

代码思路

  • 首先定义了一些常量和数组,N 表示最大可能的珠子数量,INF 是一个很大的常数表示无穷大,w 数组用于存储珠子的标记值,f 数组用于存储不同区间聚合的最大能量。
  • 输入珠子的数量 n 后,将珠子的标记值读入,并进行了一个循环处理,将原序列重复一遍,这样便于处理环形的情况。
  • 然后通过三重循环来计算动态规划数组 f。最外层循环表示区间长度,从 2 开始递增到 n+1。对于每个确定长度的区间,通过内层的两个循环确定左右端点 lr,再通过中间的 k 遍历所有可能的分割点,计算当前区间在不同分割情况下的最大能量,并更新 f[l][r]
  • 最后通过一个循环找到所有长度为 n 的区间(对应原环形序列的一圈)中的最大能量值并输出。

总的来说,这段代码通过动态规划的方法逐步计算出所有区间的最优聚合能量,最终得到整个序列的最大能量。

改进思路

  1. 可以考虑添加一些注释提高代码的可读性。
  2. 对于一些重复计算的部分,可以进一步优化计算逻辑,避免不必要的重复计算。

改进代码

cpp 复制代码
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
int n;
int w[N];
int f[N][N];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> w[i];
        w[i + n] = w[i]; // 将序列重复,处理环形情况
    }
    for (int len = 2; len <= n + 1; len ++ )
        for (int l = 1; l + len - 1 <= n * 2; l ++ )
        {
            int r = l + len - 1;
            for (int k = l + 1; k < r; k ++ )
            {
                // 计算并更新最大能量
                f[l][r] = max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);
            }
        }
    int res = 0;
    for (int l = 1; l <= n; l ++ ) 
        res = max(res, f[l][l + n]); // 找到环形一圈的最大能量
    cout << res << endl;
    return 0;
}
相关推荐
Beau_Will几秒前
数据结构-树状数组专题(1)
数据结构·c++·算法
迷迭所归处4 分钟前
动态规划 —— 子数组系列-单词拆分
算法·动态规划
爱吃烤鸡翅的酸菜鱼5 分钟前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
寻找码源1 小时前
【头歌实训:利用kmp算法求子串在主串中不重叠出现的次数】
c语言·数据结构·算法·字符串·kmp
Matlab精灵1 小时前
Matlab科研绘图:自定义内置多款配色函数
算法·matlab
诚丞成1 小时前
滑动窗口篇——如行云流水般的高效解法与智能之道(1)
算法
带多刺的玫瑰2 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔2 小时前
《线性代数的本质》
线性代数·算法·决策树
yigan_Eins3 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
阿史大杯茶3 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法