当前位置: 首页 > news >正文

区间 DP 详解

文章目录

  • 区间 DP
    • 分割型
    • 合并型
      • 环形合并

区间 DP

区间 DP,就是在对一段区间进行了若干次操作后的最小代价,一般是合并和拆分类型。

分割型

分割型,指把一个区间内的几项分开拆成一份一份的,再全部合起来就是当前答案,可以理解为合并型的另一种(合并型详见下面),它的时间复杂度一般为 O ( n 3 ) O(n^3) O(n3),其中我们一般设 dp[i][j] 表示把前 i i i 项拆成 j j j 份的最小值,而第三层循环则是循环的分割点,表示把前 i i i 项从 k k k 这里分开来看,这就是“分割”。由此,我们可以得到这样一个状态转移方程:

d p i , j = min ⁡ { d p i , j , d p k , j − 1 + 代价 } dp_{i,j}=\min\{dp_{i,j},dp_{k,j-1}+\text{代价}\} dpi,j=min{dpi,j,dpk,j1+代价}

当然,有时我们也会把这个 k k k 当做从 i − 1 i-1 i1 i i i 一共分成了 k k k 份,由此,我们可以得到另一种状态转移方程:

d p i , j = d p i − 1 , j − k + d p i , j dp_{i,j}=dp_{i-1,j-k}+dp_{i,j} dpi,j=dpi1,jk+dpi,j

这种情况下一般就不是讨论最小值,而是计数,我们可以看下面这道例题:

⼩明的花店新开张,为了吸引顾客,他想在花店的⻔⼝摆上⼀排花,共 m m m 盆。通过调查顾客的喜好,⼩明列出了顾客最喜欢的 n n n 种花,从 1 1 1 n n n 标号。为了在⻔⼝展出更多种花,规定第 i i i 种花不能超过 a i a_i ai 盆,摆花时同⼀种花放在⼀起,且不同种类的花需按标号的从⼩到⼤的顺序依次摆列。试编程计算,⼀共有多少种不同的摆花⽅案。

这就是我们刚刚说的第二种类型,直接套公式即可。注意初始化(初始化就不用我都说了吧,就是 d p i , 0 = 1 dp_{i,0}=1 dpi,0=1 呗),但这种类型很少见,我基本翻遍了全网才找到了这一道题,其余的基本都是第一种,所以各位同学终点记第一种就行,第二种做一个拓展。

AC 代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e6+7;
int n,m,a[106],dp[106][106];
signed main()
{
	cin>>n>>m;
	dp[0][0]=1;//初始化
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		dp[i][0]=1;
	}
	for(int i=1;i<=n;i++)//终点
	{
		for(int j=1;j<=m;j++)//分割份数
		{
			for(int k=0;k<=min(a[i],j);k++)//i-1 到 i 的分割份数
			{
				dp[i][j]=(dp[i][j]+dp[i-1][j-k])%mod;
			}
		}
	}
	cout<<dp[n][m];
	return 0;
}

下面留一道例题供大家练习:

今年是国际数学联盟确定的“2000――世界数学年”,⼜恰逢我国著名数学家华罗庚先⽣诞⾠ 90 周年。在华罗庚先⽣的家乡江苏⾦坛,组织了⼀场别开⽣⾯的数学智⼒竞赛的活动,你的⼀个好朋友 XZ 也有幸得以参加。活动中,主持⼈给所有参加活动的选⼿出了这样⼀道题⽬:

设有⼀个⻓度为 N N N 的数字串,要求选⼿使⽤ K K K 个乘号将它分成 K + 1 K+1 K+1 个部分,找出⼀种分法,使得这 K + 1 K+1 K+1 个部分的乘积能够为最⼤。

同时,为了帮助选⼿能够正确理解题意,主持⼈还举了如下的⼀个例⼦:

有⼀个数字串: 312 312 312, 当 N = 3 , K = 1 N=3,K=1 N=3,K=1 时会有以下两种分法:

  1. 3×12=36
  2. 31×2=62

这时,符合题⽬要求的结果是: 31 × 2 = 62 31\times2=62 31×2=62

现在,请你帮助你的好朋友 XZ 设计⼀个程序,求得正确的答案。

合并型

合并型,一般指把这一个区间内的相邻两项合在一起,每次代价为这两项的和,求最小代价。 这种题,我们只需要一个万能公式就可以搞定,它就是:

d p j , e d = min ⁡ { d p j , e d , d p j , k + d p k + 1 , e d + 代价 } dp_{j,ed}=\min\{dp_{j,ed},dp_{j,k}+dp_{k+1,ed}+\text{代价}\} dpj,ed=min{dpj,ed,dpj,k+dpk+1,ed+代价}

咳咳,不小心把祖传秘方给说出来了。

其中 j j j 是起点, e d ed ed 是终点, k k k 是分割点,表示从起点到终点中间一个把区间一分为二的点。这时再回去看看那个方程,是不是就明了多了?

这里我要说一下:这里我们一般写三层循环,最外面枚举区间长度,第二层枚举起点,第三层枚举分割点。所以时间复杂度也是 O ( n 3 ) O(n^3) O(n3)

还有就是 DP 的初始化我们一般都是这样:

for(int i=1;i<=n;i++)
{
	dp[i][i]=0;
}

表示你以当前这个点为起点同时为终点合并的代价为 0 0 0。当然,在不同的题中有不同的初始化,一般都是两个区间之间的代价等于多少这种。

为了让大家更好理解,我们拿一道例题来讲一讲(没有洛谷的可以看下面):

设有 N ( 0 ≤ N ≤ 300 ) N(0\le N\le300) N(0N300) 堆石子排成一排,其编号为 1 , 2 , 3 , … , N 1,2,3,\dots,N 1,2,3,,N。每堆石子有一定的质量 m i ( m i ≤ 1000 ) m_i(m_i\le1000) mi(mi1000)。现在要将这 N N N 堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻。合并时由于选择的顺序不同,合并的总代价也不相同。试找出一种合理的方法,使总的代价最小,并输出最小代价。

一道极其经典的合并型区间 DP,让我们套上上面的模版并把代价加入得(其中 s i s_i si m i m_i mi 的前缀和):

d p j , e d = min ⁡ { d p j , e d , d p j , k + d p k + 1 , e d + s e d − s j } dp_{j,ed}=\min\{dp_{j,ed},dp_{j,k}+dp_{k+1,ed}+s_{ed}-s_j\} dpj,ed=min{dpj,ed,dpj,k+dpk+1,ed+sedsj}

AC 记录

(代码自己写)。

环形合并

有时候,我们这个合并型可能会在一个环上合并,这时我们不好考虑首位合并的情况,所以就有一种办法:断环成链!我们只需要把这个环变成一条链,然后在后面再接上一次,就可以正常的跑合并型了,具体请看这张图:

[ a 1 , a 2 , a 3 , … , a n ] , a n + 1 , a n + 2 , … , a 2 n [a_1,a_2,a_3,\dots,a_n],a_{n+1},a_{n+2},\dots,a_{2n} [a1,a2,a3,,an],an+1,an+2,,a2n
a 1 , [ a 2 , a 3 , … , a n , a n + 1 ] , a n + 2 , … , a 2 n a_1,[a_2,a_3,\dots,a_n,a_{n+1}],a_{n+2},\dots,a_{2n} a1,[a2,a3,,an,an+1],an+2,,a2n
a 1 , a 2 , [ a 3 , … , a n , a n + 1 , a n + 2 ] , … , a 2 n a_1,a_2,[a_3,\dots,a_n,a_{n+1},a_{n+2}],\dots,a_{2n} a1,a2,[a3,,an,an+1,an+2],,a2n
⋯ \cdots
a 1 , a 2 , a 3 , … , a n , [ a n + 1 , a n + 2 , … , a 2 n ] a_1,a_2,a_3,\dots,a_n,[a_{n+1},a_{n+2},\dots,a_{2n}] a1,a2,a3,,an,[an+1,an+2,,a2n]

(有点抽象,但是因为设备太简陋了,也只能这样做。)

我们还是来看一道例题:

点我跳转至例题。

因为题目是一个 pdf 文件,不好取字,所以就把链接放在上面了,偷懒的同学也可以看下面的图片。

这就是一个很经典的环形区间 DP,所以我们可以先断环成链,然后在后面拼上一截,最后直接做区间合并型 DP 就行了。

但要注意的是这里的初始化不是 0 0 0,而是从 i − 1 i-1 i1 i + 1 i+1 i+1 的值为 ∏ k = i − 1 i + 1 a k \prod_{k=i-1}^{i+1}a_k k=i1i+1ak ∏ \prod 是多个数的乘积的意思)

AC 代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,mx,a[206],dp[206][206];//dp:从i到j的最大方案 
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		a[i+n]=a[i];//断环成链并拼上一节,方便后续处理
	}
	n*=2;
	for(int i=2;i<n;i++)//初始化
	{
		dp[i-1][i+1]=a[i-1]*a[i]*a[i+1];
	}
	for(int i=4;i<=n;i++)//长度 
	{
		for(int j=1;j<=n-i+1;j++)//起点 
		{
			int ed=i+j-1;
			for(int k=j+1;k<ed;k++)//分割点 
			{
				dp[j][ed]=max(dp[j][ed],dp[j][k]+dp[k][ed]+a[j]*a[k]*a[ed]);//这里注意:是 dp[j][k]+dp[k][ed] 而不是 dp[j][k]+dp[k+1][ed],具体原因大家自己想
			}
		}
	}
	n/=2;
	for(int i=1;i<=n;i++)
	{
		mx=max(mx,dp[i][i+n]);
	}
	cout<<mx;
	return 0;
}

相关文章:

  • XMLHttpRequest vs Fetch API:一场跨越时代的“浏览器宫斗剧“
  • 什么是软件测试(目的、意义、流程)
  • STM32在裸机(无RTOS)环境下,需要手动实现队列机制来替代FreeRTOS的CAN发送接收函数
  • 第四篇:系统分析师——12-16章
  • 《线性表、顺序表与链表》教案(C语言版本)
  • JavaScript性能优化(上)
  • 观成科技:利用DoH加密信道的C2流量分析
  • react实现SVG地图区域中心点呈现圆柱体,不同区域数据不同,圆柱体高度不同
  • oracle 存储体系结构
  • 【Python基础】散列类型
  • docker 中跑faster-whisper 教程(1050显卡)
  • VGA接口设计
  • 【工具使用】在OpenBMC中使用GDB工具来定位coredump原因
  • 【vue】v-bind=“$attrs“理解与使用
  • MPDrive:利用基于标记的提示学习提高自动驾驶的空间理解能力
  • 数据赋能——个人信息安全与大数据决策创新
  • 无法看到新安装的 JDK 17
  • ROS2_control 对机器人控制(不完整,有时间再更新)
  • 2025-04-08 NO.4 Quest3 交互教程
  • 算法(二十一)
  • 特朗普访问卡塔尔,两国签署多项合作协议
  • 马上评|让查重回归促进学术规范的本意
  • “大鼻子情圣”德帕迪约因性侵被判缓刑,还有新的官司等着他
  • 习近平出席中国-拉美和加勒比国家共同体论坛第四届部长级会议开幕式
  • 书法需从字外看,书法家、学者吴本清辞世
  • 水豚“豆包”出逃已40天,扬州茱萸湾景区追加悬赏