关于动态规划

关于动态规划

目录 Content

基础

  • 概述
  • DP是什么
  • DP问题特征
  • DP问题的一般思考方法
  • 线性DP
  • 例1 数字三角形
  • 例2 最长公共子序列LCS
  • 背包DP
  • 01背包
  • 完全背包
  • 多重背包&优化
  • 分组背包
  • 求方案数

Part 1 概述

1.DP是什么

本章将介绍介绍动态规划(Dynamic Programming, DP)及其解决的问题、根据其设计的算法及优化。

动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

由于动态规划并不是某种具体的算法,而是一种解决特定问题的方法 ,因此它会出现在各式各样的数据结构中,与之相关的题目种类也更为繁杂。OI-Wiki

DP是一种将一个问题划分为子问题来解决的一种思路。

2.DP问题的特征

DP问题有三个特征:

  1. 最优子结构性质

最优子结构性质,简单地说就是最优解可以被划分为若干子问题的解的性质。

  1. 无后效性

已经求解过的子问题,不会再受到以后决策的影响

  1. 子问题重叠

如果有大量的重叠子问题,我们可以用空间将这些子问题的解存储下来,避免重复求解相同的子问题,从而提升效率。

其中,1和2是使用DP所必须具备的性质,而3是DP效率的关键

3.DP问题的一般思考方法

  1. 分解子问题
  2. 抽取状态
  3. 思考初态和最终态及初始化
  4. 寻找状态之间的关系,得出转移方程

(呐,作者自己提取的,可能不太对)

Part 2 线性DP

线性dp是最基础的dp,其实其他模型都是建立在他的基础上,然后或多或少有点优化或者共性从而被整合成另外的模型。

例1 数字三角形

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

        7 
      3   8 
    8   1   0 
  2   7   4   4 
4   5   2   6   5 

在上面的样例中,从 73875 的路径产生了最大

对于 100% 的数据,1r1000,所有输入在 [0,100] 范围内。IOI1994 Day1T1

首先,这种走路类型的问题描述显然可以被分为一步一步的,我们可以把每一步所在位置看作当前问题的状态,而初态就是在金字塔顶端,最终态就是在最底层。

	dp[1][1]=mp[1][1];
	for(int i=2;i<=r;i++)
	{
		for(int j=1;j<=i;j++)
		{
			dp[i][j]=max(dp[i-1][j]+mp[i][j],dp[i-1][j-1]+mp[i][j]);
		}
	}

要注意的是,首先要将初始位置加上初分数。最后由于不知道在最后一排的哪里终止,答案要取最大值。

例2 最长公共子序列LCS

给定一个长度为n的序列A和一个长度为m的序列B,求出一个最长的序列,使得该序列既是A的子序列,也是B的子序列,这样的序列就是LCS。由于序列可能不唯一,你只要输出它的长度。

别慌,先考虑人是怎么思考的。例如11345和37145

我们是一个一个依次考虑每一个A、B中的数,随时看是否可以构成LCS。

我们DP也可以借鉴这样的思路:
设f(i,j)表示只考虑A的前i个元素,B的前j个元素时的最长公共子序列的长度,求这时的最长公共子序列的长度就是子问题。f(i,j)就是我们所说的 状态,则f(n,m)是最终要达到的状态,即为所求结果。

对于每个f(i,j),存在三种决策:如果Ai=Bj,则可以将它接到公共子序列的末尾;另外两种决策分别是继承Ai或者Bj。状态转移方程如下:
f(i,j)={f(i1,j1)+1Ai=Bjmax(f(i1,j),f(i,j1))AiBj

Part 3 背包DP

背包是一种特殊的DP模型。

1.01背包

有N件物品和一个容量为V 的背包。放入第i件物品耗费的空间是Ci,得到
的价值是Wi。求解将哪些物品装入背包可使价值总和最大。

这是最基础的背包问题。同样,我们根据生活常识,可以一件一件地选择放不放

设f(i,v)表示前i件物品恰放入一个容量为v的背包可以
获得的最大价值。

若只考虑第i件物品的策略(放或不放),那么就可以转化
为一个只和前i − 1件物品相关的问题。

  • 如果不放第i件物品
    那么问题就转化为“前i − 1件物品放入容量为v的背包中”,可以直接继承价值为f(i − 1,v)
  • 如果放第i件物品
    那么问题就转化为“前i − 1件物品放入剩下的容量为vCi的背包中”,此时能获得的价值就是前面的最大值再加上通过放入第i件物品获得的价
    Wi

则其状态转移方程便是:
f(i,v)=maxf(i1,v),f(i1,vCi)+Wi

优化空间复杂度

可以发现,状态转移只用到了i和i-1层的状态,所以我们就不再需要存储其他状态。

需要注意的是,在实现上,循环顺序应该是怎么样的?能不能去掉一维然后直接像上面那样写呢?

不能。 因为如果去掉第一维再按顺序转移,设当前是f(i,j),那么我们就分不清我们使用的是之前还没有选择过i的,还是已经选择了i的状态f(i,j-Wi)有可能导致重复选择物品i

代码:

for (int i = 1; i <= n; i++)
  for (int l = W; l >= w[i]; l--) f[l] = max(f[l], f[l - w[i]] + v[i]);

2.完全背包

有N种物品和一个容量为V 的背包,每种物品都有无限件可用。放入第i种
物品的耗费的空间是Ci,得到的价值是Wi。求解:将哪些物品装入背包,可使
这些物品的耗费的空间总和不超过背包容量,且价值总和最大。

这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从
每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、
取1件、取2件……直至取⌊V /Ci⌋件等很多种。

如果简单地拆分每件物品至V/Ci件,那么时间复杂度会很大。

那么我们就想到我们之前写01优化时写出的错误代码,当时这种写法会导致物品被重复选择。诶?这不就是完全背包需要的吗?

代码:

for (int i = 1; i <= n; i++)
  for (int l = w[i]; l<=W; l++) f[l] = max(f[l], f[l - w[i]] + v[i]);

3.多重背包

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费
的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间
总和不超过背包容量,且价值总和最大。

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略
微一改即可。只要把物品直接拆分成Mi件就好,实现上只要加一重循环。

优化

其实可以发现,有的多余拆分其实是无效的。所以我们变化拆分策略:

将第i种物品分成若干件01背包中的物品,其中每件物品有一个系
数。这件物品的费用和价值均是原来的费用和价值乘以这个系数。令这些系数
分别为1, 2, 2
2
. . . 2
k−1
, Mi − 2
k + 1,且k是满足Mi − 2
k + 1 > 0的最大整数。例
如,如果Mi为13,则相应的k = 3,这种最多取13件的物品应被分成系数分别
为1, 2, 4, 6的四件物品。
分成的这几件物品的系数和为Mi,表明不可能取多于Mi件的第i种物品。另
外这种方法也能保证对于0 . . . Mi间的每一个整数,均可以用若干个系数的和表
示。

这其实用到一个需要数学证明的结论。

二进制分组代码:

// OI-wiki Version
index = 0;
for (int i = 1; i <= m; i++) {
  int c = 1, p, h, k;
  cin >> p >> h >> k;
  while (k - c > 0) {
    k -= c;
    list[++index].w = c * p;
    list[index].v = c * h;
    c *= 2;
  }
  list[++index].w = p * k;
  list[index].v = h * k;
}

4.分组背包

有N件物品和一个容量为V 的背包。第i件物品的费用是Ci,价值是Wi。这
些物品被划分为K组,每组中的物品互相冲突,最多选一件。求解将哪些物品
装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件
都不选。


有:
F[k, v] = max{F[k − 1, v], F[k − 1, v − Ci
] + Wi
| item i ∈ group k}

也就是说,依次看看每一组中的物品是否加入。

代码:

// OI-wiki Version
for (int k = 1; k <= ts; k++)          // 循环每一组
  for (int i = m; i >= 0; i--)         // 循环背包容量
    for (int j = 1; j <= cnt[k]; j++)  // 循环该组的每一个物品
      if (i >= w[t[k][j]])
        dp[i] = max(dp[i],
                    dp[i - w[t[k][j]]] + c[t[k][j]]);  // 像0-1背包一样状态转移

5.求方案数

对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖等)
的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得
到装满背包或将背包装至某一指定容量的方案总数。
对于这类改变问法的问题,一般只需将状态转移方程中的取最大值改成求和即
可。

posted @   haozexu  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示