关于动态规划的一些理解

Toretto·2024-08-10 17:11·8 次阅读

关于动态规划的一些理解

关于动态规划的一些理解

1. 什么是动态规划#

动态规划(DP,Dynamic Programming)是一种解决问题的方法。它通过将难以实现的整体的大问题划分成简单的局部的小问题。最后将小问题一一求解以完成问题。

对于动态规划能否使用有一些限制,这些限制我推荐参看动态规划基础 - OI Wiki中的描述。(实际上是不是用DP看一眼题就知道了)。

动态规划只是一种解决问题的方法,它并不是类似于DFS(深度优先搜索)有特定实现的算法,具体问题具体分析。

2. 常见的动态规划类型以及理解#

2.1 背包DP#

背包问题是动态规划的一个分支。对于大部分人而言,背包DP更容易理解、入门动态规划。

背包问题本质是在多个种类物品之间做选择。由于[重量]等原因对于选择做了限制,通常求选择的最优解。

2.1.1 01背包#

01背包是背包问题的一种。该类问题需要在多个种类的物品之间做出选择,每个种类只能选择一个,由于“选与不选”的特性,类似于二进制中的01[真假],所以称为01背包。

下面对于01背包一个例题:

P2871 [USACO07DEC] Charm Bracelet S - 洛谷

题目大意:

N 件物品和一个容量为 M 的背包。第 i 件物品的重量是 Wi,价值是 Di。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

输入样例:

Copy
4 6
1 4
2 6
3 12
2 7

输出样例:

Copy
23

解题思路:

这是一道01背包问题的模板题。对于容量 M 的背包,装 x 个物品使其总价值 D 最大。最先想到的可能是贪心。但是由于重量的存在,使得价值最高可能不是收益最高。高价值高重量的物品可能不及多个低价值低重量物品之和。所以应该使用动态规划

假设我们有 f(u,v) 。它表示拿前 u 件物品且剩余容量是 v 时能拿到的最大值,不难看出,f(N,M) 是这道题的答案。

那么我们就将求这道题的答案转化为了求 f(N,M)。我们首先需要把 f(u,v) 表示出来。

我们只关注于第 u 件物品,它有取与不取两种可能。若是取,则剩余 vWi 的空间,获得了 Di 价值的物品。若是不取,则空间没有变化,得到的价值也没有变化。

不管取了还是不取,我们都对第u件物品做出了决策。我们需要在两个决策中选择一个最好的。所以我们需要知道两个决策的价值并比较。

对于取了的情况,我们得到的价值是剩余的空间取前 u-1 个物品的最大价值加上这件物品的价值。即 f(u1,vDi)+Wi .

对于没有取的情况,我们得到的价值是目前的空间前 u-1 个物品的最大价值。即 f(u1,v) .

最后,我们对两个决策之间选择一个最大值,就是 f(u, v) 的最优解

f(u,v)=max{f(u1,vDi)+Wi,f(u1,v)}

这就是这道题的状态转移方程。

由于这道题是模板题,实际上所有的01背包方程都是基于这个方程推导出来的。

核心代码:

Copy
// C++
for(int i = 1; i <= N; i++) // 循环从1开始,可以避免处理越界行为,DP默认初始化0
{
for(int j = 1; i <= M; j++) // 列举背包空间的情况
{
if (j < D[i]) // 在实际代码中,会有无法取的情况,这种情况就是不取
{
DP[i][j] = DP[i-1][j]; // 不取
}
else
{
DP[i][j] = std::max(DP[i-1][j-D[i]] + W[i], DP[i-1][j]); // 状态转移方程
}
}
}

优化:

仔细观察状态转移方程。发现对于 f(u,v) 有影响的只有 u1 ,所以我们就可以将状态转移方程压缩为一维,即一个参数。

假设有状态 f(v) 表示剩余空间为 v 时的最优解,写出状态转移方程:

f(v)=max{f(v),f(vDi)+Wi}

优化后的核心代码:

Copy
for(int i = 1; i <= N; i++)
{
for(int j = M; j >= W[i]; j--)
{
DP[j] = std::max(DP[j], DP[j - D[i]] + W[i]); // 状态转移方程
}
}

一些练习题:

[P1049 NOIP2001 普及组] 装箱问题 - 洛谷

[P1048 NOIP2005 普及组] 采药 - 洛谷

2.1.2 完全背包#

完全背包是背包问题的一种。它与01背包不同,01背包每类物品只能取一次,而完全背包每类物品能取无数次。

实际上,由于“背包容量”的限制,实际能取到的数量是有限的,这也是需要决策取舍的地方。

例题:

P1616 疯狂的采药 - 洛谷

题目大意:

在总共 t 的时间内,对 m 种草药中进行无数量限制的采摘。对于每类草药,有 AiBi 分别表示草药的采集时间和价值。

解题思路:

这是一个完全背包问题。我们将决策从取与不取转换到取多少。我们用 k 来表示取 k 个草药。

有状态 f(u,v) 。表示在剩余背包空间为 v 时对前 u 种草药做决策后的最优解。不难发现,f(m,t) 是本题答案。

如果对第 u 种草药取了 k 个,那么获得的收益为:

f(u1,vk×Ai)+k×Bi

特别地,如果没有取,则 k=0 ,所以 k 的取值范围是 [0,+) .

状态转移方程:

f(u,v)=maxk=0{f(u1,vk×Ai)+k×Bi}

核心代码:

Copy
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= t; j++)
{
for(int k = 0; k * A[i] <= j; k++) // 如果不能取就没有必要增加取的数量
{
DP[i][j] = std::max(DP[i][j], DP[i-1][j-k*A[i]]+k*B[i]); // 状态转移方程
}
}
}

这是一个 O(n3) 的算法。是否能像01背包一样优化?

实际上,对于 f(u,v) 可以不枚举 k ,即:

f(u,v)=max{f(u1,v),f(u1,vAi)+Bi}

对于核心代码,与01背包的枚举顺序是相反的:

Copy
for(int i = 1; i <= m; i++)
{
for(int j = 0; j <= t - A[i]; j++) // 与01背包相反的枚举顺序
{
DP[j + A[i]] = std::max(DP[j] + B[i], DP[j + A[i]]); // 状态转移方程
}
}

时间复杂度 O(n2) .

2.2 区间DP#

区间DP是对一个区间进行合并,求合并操作后的最优解。

区间DP最经典的例题就是石子合并。实际上这道题还可以进行四边形不等式优化,在这里不展开,参见后文DP优化。

例题:

P1880 [NOI1995] 石子合并 - 洛谷

题目大意:

在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。求得分的最大值和最小值。

样例输入:

Copy
4
4 5 9 4

样例输出:

Copy
43
54

解题思路:

这是一道区间DP模板题。非常经典。我们先不考虑环形的问题。假设他是直线的。

对于这道题,有状态转移方程 f(i,j) 表示在区间 ij 之内合并后的最大值。显然,对于这道题的无环情况,f(0,N1) 是答案。

仍然是表示状态转移方程。对于区间 ij ,我们假设由一个下标 k 。它将区间 ijk 分隔开成两份。左右两边份分别合并成一堆,最后整体合并成一堆。那么求 k 的最优解,就是求 f(i,j) 的最优解。

我们使用枚举法:

f(i,j)=max{f(i,k)+f(k+1,j)+cost}

其中:kij1 进行枚举。cost是合并 ij 后获得的收益。对于此题,cost=n=ijAnAn 表示第 n 堆小石子的数量。

然后设 sumA 的前缀和。状态转移方程转化为:

f(i,j)=max{f(i,k)+f(k+1,j)+sum(j)sum(i1)}

随后我们设 length=ji1 。那么枚举所有的 length 。随后枚举所有的 i ,求出 j 。列举所有的 k 进行状态转移。时间复杂度 O(n3)

如果你看到这里想要试一试,不妨尝试这道简化版无环:

P1775 石子合并(弱化版) - 洛谷

如何处理环?

我们将原数组 A 扩展到原来的二倍。An=A2n 。随后我们使用动态规划求解。最终的答案为:

answer=max{f(a+1,a+n)}(a[0,N2])

核心代码:

Copy
for(int l = 2; l <= N; l ++) // 枚举所有可能的长度
{
for(int i = 1; i <= 2 * N + 1 - l; i++) // 枚举所有的i
{
int j = l + i - 1; // 通过长度与i计算j
for(int k = i; k < j; k++) // 枚举所有可能的k
{
DP[i][j] = std::max(DP[i][j], DP[i][k] + DP[k + 1][j] + sum[j] - sum[i - 1]); // 状态转移方程
}
}
}
posted @   LiserverYang  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示
目录