区间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;
}
相关推荐
MORE_7712 分钟前
leecode-灌溉花园-贪心算法and动态规划
算法·贪心算法·动态规划
kyle~17 分钟前
C++--- dlsym 调用封装好的算法动态库的核心工具 <dlfcn.h>
开发语言·c++·算法
似水এ᭄往昔22 分钟前
【初阶数据结构】--排序算法
数据结构·算法·排序算法
2301_7811435626 分钟前
C语言笔记(四)
c语言·笔记·算法
算法-大模型备案 多米38 分钟前
算法备案算法安全自评估报告模板(精简完善版)
大数据·网络·人工智能·算法·文心一言
Frostnova丶41 分钟前
LeetCode 238 & 2906.构造乘积数组与乘积矩阵
算法·leetcode·矩阵
张槊哲44 分钟前
拆解大语言模型(LLM)的底层推演、架构演进与工业落地
算法
sali-tec1 小时前
C# 基于OpenCv的视觉工作流-章41-模板匹配
图像处理·人工智能·opencv·算法·计算机视觉
进击的小头1 小时前
第16篇:系统的稳定裕度分析
python·算法
黎阳之光1 小时前
AI数智筑防线 绿色科技启新篇,如何用硬核技术赋能生态安全双升级
人工智能·科技·算法·安全·数字孪生