区间 dp 介绍

今天给大家简单介绍区间 dp,以及 c++ 代码。

区间 dp

区间 dp 介绍

什么是区间 dp?

区间 dp 就是在区间上进行动态规划,求解一段区间上的最优解。

原理

通过枚举分割点得到并更新小区间的最优解,然后再通过合并小区间得到大区间的最优解。

什么时候要用区间 dp?

一般一下特征的问题适用于区间 dp:
  • 问题范围是区间‌:例如字符串的子串、数组的子序列、数轴上的区间等。
  • 决策影响整个区间‌:操作(如合并、分割、删除)会影响区间内的所有元素。
  • 子问题可重复分割‌:大区间的最优解依赖小区间的最优解,且子问题需要多次计算。
常见的区间 dp 问题:
  • 合并石子
  • 分割字符串使子串满足条件
  • 回文类问题
  • ……

区间 dp 的模板

for(int len = 1; len <= n; len++)
{ //枚举长度
	for(int j = 1; j+len <= n+1; j++)
	{ //枚举起点,ends<=n
		int ends = j + len - 1;
		for(int i = j; i < ends; i++)
		{ //枚举分割点,更新小区间最优解
			dp[j][ends] = min(dp[j][ends], dp[j][i] + dp[i + 1][ends] + something);
		}
	}
}

例题 CF-245H

【题目传送门】

中文题面

回文串数量查询
  • 每个测试的时间限制 5 5 5
  • 每个测试的内存限制 256 256 256 兆字节
题目描述

你有一个长度为 ∣ s ∣ |s| s 的字符串 s = s 1 , s 2 , … s ∣ s ∣ s=s1,s2, \dots s|s| s=s1,s2,ss,由小写英文字母组成。同时有 q q q 个查询,每个查询由两个整数 l i , r i l_i,r_i li,ri 描述( 1 ≤ l i ≤ r i ≤ ∣ s ∣ 1\le l_i\le r_i≤|s| 1liris)。该查询的答案是字符串 s [ l i … r i ] s[l_i\dots r_i] s[liri] 的子串中,回文串的数量。

字符串 s [ l … r ] = s l , s l + 1 , ⋯ s r s[l\dots r]=s_l,s_l+1,⋯s_r s[lr]=sl,sl+1,sr 1 ≤ l ≤ r ≤ ∣ s ∣ 1\le l\le r\le|s| 1lrs)是字符串 s = s 1 , s 2 , … , ⋯ s ∣ s ∣ s=s1,s2,\dots,⋯s|s| s=s1,s2,,ss 的一个子串。

字符串 t t t 被称为回文串,当且仅当它从左到右和从右到左读起来是一样的。 形式上,如果 t = t 1 , t 2 , … t ∣ t ∣ = t ∣ t ∣ , t ∣ t ∣ − 1 , … t 1 t=t_1,t_2,\dots t_|t|=t|t|,t|t|−1,\dots t_1 t=t1,t2,tt=tt,tt1,t1

输入

第一行包含字符串 s s s 1 ≤ s ≤ 5000 1\le s\le 5000 1s5000)。第二行包含一个整数 q q q 1 ≤ q ≤ 106 1\le q\le 106 1q106),表示查询的数量。接下来的 q q q 行包含这些查询。其中第 i i i 行包含两个用空格分隔的整数 l i , r i l_i,r_i li,ri 1 ≤ l i ≤ r i ≤ ∣ s ∣ 1\le l_i\le r_i\le|s| 1liris)表示第 i i i 个查询的描述。

保证给定的字符串仅由小写英文字母组成。

输出

输出 q q q 个整数,表示查询的答案。按照输入中查询的顺序打印答案。用空格分隔打印出的数字。

示例
  • 输入示例
caaaba
5
1 1
1 4
2 3
4 6
4 5
  • 输出示例
1
7
3
4
2
说明

考虑第一个测试用例中的第四个查询。字符串 s [ 4 … 6 ] s[4\dots6] s[46] = aba。它的回文子串是:abaaba

思路

首先这道题需要求出区间 [ l , r ] [l,r] [l,r] 内有多少个回文子串,所以可以想到区间 dp。

我们规定 dp 状态: d p i , j = k dp_{i,j}=k dpi,j=k 表示在区间 [ i , j ] [i,j] [i,j] 里回文子串有 k k k 个。

接着我们还需要一个数组 c n t i j cnt_{ij} cntij 来记录左端点 ≥ i \ge i i,右端点等于 j j j 有多少个回文子串。

那么就可以得到转移方程: d p i , j = d p i , j − 1 + c n t i , j \color{red}dp_{i,j}=dp_{i,j-1}+cnt_{i,j} dpi,j=dpi,j1+cnti,j

于是我们的问题就变成了:如何用 Θ ( n 2 ) \Theta(n^2) Θ(n2) 得到 c n t cnt cnt 数组。

我们定义 f i , j f_{i,j} fi,j 表示 [ i , j ] [i,j] [i,j] 这个子串是不是回文串。

然后我们发现,如果这个 [ i + 1 , j − 1 ] [i+1,j-1] [i+1,j1] 这个子串是回文串且第 i i i 位和 j j j 位相同(如下图所示),那么 [ i , j ] [i,j] [i,j] 就是一个回文串。

pic

于是我们就得到了 f f f 数组:

for(int i = len; i >= 1; i--)
	for(int j = i; j <= len; j++)
		f[i][j] = (f[i+1][j-1] && s[i] == s[j]);

接下来我们来想如何得到 c n t cnt cnt

也很简单,就是先枚举 j j j,因为 j j j 是固定的,然后按 j → 1 j\to 1 j1 枚举 i i i。因此在计算 c n t i , j cnt_{i,j} cnti,j 是我们已经得到了 c n t i + 1 , j cnt_{i+1,j} cnti+1,j,而 c n t i , j cnt_{i,j} cnti,j 就是 c n t i + 1 , j cnt_{i+1,j} cnti+1,j 加上 [ i , j ] [i,j] [i,j] 是否为回文串,也就是 f i , j f_{i,j} fi,j,那么转移方程就是 c n t i , j = c n t i + 1 , j + f i , j \color{red}cnt_{i,j}=cnt_{i+1,j}+f_{i,j} cnti,j=cnti+1,j+fi,j

于是预处理 c n t cnt cnt

for(int j = 1; j <= len; j++)
	for(int i = j; i >= 1; i--)
		cnt[i][j] = cnt[i+1][j] + f[i][j];

最后 d p dp dp 就很简单了:

for(int i = 1; i <= len; i++)
	for(int j = i; j <= len; j++)
		dp[i][j] = dp[i][j-1] + cnt[i][j];

这就是这道题完整地思路。

<think>嗯,用户想要详细了解区间动态规划区间DP)。根据提供的引用资料,我需要整合出一个清晰的解释框架。 引用中提到区间DP适用于字符串处理、资源优化等问题,特点是处理区间序列上的最优解。用户可能正在学习动态规划的中高级内容,需要理论结合实践的解释。 核心要点应该包括: 1. 基本概念:定义必须以区间[l,r]为状态,引用[4]的闫氏DP分析法很适合作为框架 2. 典型特征:需要强调区间长度遍历顺序这个关键点(引用[3]的环状处理也重要) 3. 必须包含经典例题,石子合并和回文子序列最合适(引用[1][2]都提到了) 特别注意引用[3]破环成链的技巧,这是用户可能遇到的难点。时间复杂度部分要补充大O表示法,但需用中文说明。 算法示例准备用石子合并,分步骤展示状态转移方程的推导: - 先写出基本方程 $f[l][r] = \min\limits_{k=l}^{r-1}(f[l][k] + f[k+1][r]) + sum(l,r)$ - 再用破环成链说明环形的处理 $\min\limits_{i=1}^n f[i][i+n-1]$ 应用场景部分要呼应引用[1]的编译器优化案例(matrix链乘),但改用更易懂的表述。最后问题设计要覆盖用户可能延伸的学习路径:从环形变体到记忆化搜索实现。 </think>以下是区间动态规划区间DP)的详细介绍,结合核心概念、特点、算法框架和应用场景: --- ### **一、区间DP的核心概念** 1. **定义** 区间DP以**序列的区间**为状态,定义 $f[l][r]$ 表示对区间 $[l, r]$ 进行操作的最优解或方案数。 **状态转移**通常依赖于区间内的分界点 $k$: $$f[l][r] = \min/\max \limits_{l \leq k < r} \left\{ f[l][k] + f[k+1][r] + cost(l, r) \right\}$$ 2. **关键特征** - **区间长度递增**:外层循环按区间长度从小到大遍历(长度 $len$ 从 1 到 $n$)。 - **枚举分界点**:内层循环枚举区间内的分割点 $k$,合并左右子区间的最优解。 - **附加代价**:合并时需计算区间操作的额外代价 $cost(l, r)$(如合并石子的重量和)。 --- ### **二、经典问题与算法步骤** #### **例1:石子合并(链状)** **问题**:合并 $n$ 堆石子,每次合并相邻两堆,代价为两堆之和,求最小总代价。 **步骤**: 1. 初始化 $f[l][r] = 0$(当 $l = r$ 时无需合并)。 2. 前缀和 $s[i]$ 快速计算区间和 $cost(l, r) = s[r] - s[l-1]$。 3. 状态转移: $$ f[l][r] = \min\limits_{k=l}^{r-1} \left\{ f[l][k] + f[k+1][r] \right\} + (s[r] - s[l-1]) $$ **代码框架**: ```python for len in range(2, n+1): # 枚举区间长度 for l in range(1, n-len+2): # 枚举左端点 r = l + len - 1 f[l][r] = float('inf') for k in range(l, r): # 枚举分界点 cost = s[r] - s[l-1] f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + cost) ``` #### **例2:环形区间处理(破环成链)** **问题**:环形石子合并(首尾相连)。 **解法**:将序列复制为 $a[1..2n]$($a[i+n] = a[i]$),在新序列上做链状DP,最终取 $\min\limits_{i=1}^{n} f[i][i+n-1]$[^3]。 #### **例3:最长回文子序列** **状态定义**:$f[l][r]$ 表示子串 $s[l..r]$ 的最长回文子序列长度。 **转移方程**: $$ f[l][r] = \begin{cases} \max\left\{ f[l+1][r], f[l][r-1] \right\} & \text{if } s[l] \neq s[r] \\ f[l+1][r-1] + 2 & \text{if } s[l] = s[r] \end{cases} $$ --- ### **三、适用场景与优化技巧** | **场景** | **问题示例** | **状态设计要点** | |-----------------------|----------------------------------|--------------------------------| | 区间合并类问题 | 石子合并、能量项链[^2] | 代价函数 $cost$ 为区间属性之和 | | 子序列/子串问题 | 最长回文子序列、括号匹配[^4] | 根据端点字符匹配设计转移 | | 环形结构问题 | 环形石子合并、凸多边形划分[^4] | 破环成链(序列长度 $\times 2$)| | 资源分配问题 | 矩阵链乘法(最小计算次数) | $cost$ 为矩阵乘法代价 | **优化技巧**: - **记忆化搜索**:递归实现避免无效状态计算(尤其适用于不规则区间)。 - **四边形不等式**:优化分界点 $k$ 的枚举范围(将内层循环复杂度从 $O(n)$ 降至 $O(1)$)。 --- ### **四、算法复杂度** - **时间复杂度**:$O(n^3)$(三重循环)。 - **空间复杂度**:$O(n^2)$(二维DP表)。 --- ### **典型应用参考** 1. **字符串处理**:回文子序列、括号匹配最大化(逆向状态设计)[^4]。 2. **游戏策略**:戳气球问题($f[l][r]$ 表示戳破 $(l,r)$ 内气球的最大得分)[^1]。 3. **编译器优化**:矩阵链乘法(最小化标量乘法次数)[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值