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

区间DP

定义

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

基本概念

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

运用情况

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

注意事项

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

解题思路

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

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

解题步骤

  1. 定义状态:明确dp数组的含义,比如dpij表示什么。
  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;
}
相关推荐
youngerwang5 分钟前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
KaMeidebaby29 分钟前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
手写码匠1 小时前
从零实现 Prompt 工程引擎:结构化提示、自动优化与多轮自省体系
人工智能·深度学习·算法·aigc
无限码力2 小时前
阿里算法岗 0530笔试真题 - 多约束条件下的元素匹配统计
算法·阿里笔试真题·阿里机试真题·阿里算法岗笔试
lqqjuly2 小时前
MLA — 多头潜在注意力深度解析
深度学习·神经网络·算法
吴可可1232 小时前
SolidWorks草图转三维DWG技巧
算法
redaijufeng3 小时前
C++雾中风景7:闭包
c++·算法·风景
小欣加油3 小时前
leetcode287寻找重复数
数据结构·c++·算法·leetcode
尽兴-4 小时前
2.1 向量基础:Embedding、余弦相似度、欧氏距离、向量检索
算法·embedding·欧氏距离·向量检索·余弦相似度
Black蜡笔小新4 小时前
自动化AI算法训练服务器DLTM训推一体工作站赋能多行业智能化升级
人工智能·算法·自动化