acwing 算法基础班学习笔记-第五讲.动态规划

动态规划的核心思想是把一个状态转移成已知状态,因此分为两部分:
acwing 算法基础班学习笔记-第五讲.动态规划_第1张图片
状态表示需要保证所有状态不重不漏,状态计算即将当前状态转变为已知状态,或保证如此转移最终能走到边界,而边界值确定。
一、背包问题

1.01背包:每件物品只能选一次
状态表示:f[i][j] 表示从前i个物品中选,重量不超过j的最大价值。
状态计算:f[i][j] =max(f[i-1][j] , f[i-1][j-v[i]] +w[i] )(当前状态可分为选了第i件物品的价值最大值和没选第i件物品的最大值)
初始i=0或j=0时,f[i][j] =0(边界条件)因此可从小到大更新f[i][j] ,由于当前的值只和上一行(i-1)的值有关,因此可以采用滚动数组,减少内存的使用,注意j要从大到小遍历,因为如果从小到大遍历的话后面f[i-1][j-v[i]]就是本轮已经更新过的值了。

2.完全背包问题:每件物品可以选无数次
状态表示:f[i][j] 表示从前i个物品中选,重量不超过j的最大价值。
状态计算:f[i][j] =max(f[i-1][j] , f[i][j-v[i]] +w[i],f[i][j-2v[i]] +2w[i],……,f[i][j-kv[i]] +kw[i] )(当前状态可分为选了1-k件第i件物品的价值最大值和没选第i件物品的最大值)
可以注意到选了一件i物品的最大价值f[i][j-v[i]] = max(f[i-1][j - v[i]],f[i][j-2v[i]] +1w[i],……,f[i][j-k*v[i]] +(k-1)*w[i] )所以其实f[i][j-v[i]]已经包含了后面选了2-k件物品i的最大价值,化简后状态转移方程为
f[i][j] =max(f[i-1][j] , f[i][j-v[i]] +w[i] )(与01背包问题很相似)
初始i=0或j=0时,f[i][j] =0(边界条件)
同样可用滚动数组更新,由于这里第二个值是本轮更新后的,因此j需要从小到大遍历。

3.多重背包问题:第i件物品最多可选si件
思路:把每件物品可选的情况拆分出来,把选择多件第i个物品转换成选择一个新的物品(如选k件则新物品表示为价值为kw[i],体积为kv[i])将问题转变为01背包问题。

4.多重背包问题II:同多重背包问题
由于s的范围增加了,若直接拆分成01问题会导致超时,因此需要优化。
优化思路:将s件拆分成s组优化为拆分为log2s(向上取整)组(最后一组数量为s-2log2s),即把拆分的组数进行二进制优化,即可将复杂度s的问题优化为logs。

5.分组背包问题:分为n组,每组有ni个物品,每组最多只能选一件物品。
思路:类似01背包问题,对于每组 s 个物品,有 s+1 种选法:fj=max(fj,fj−v0+w0,fj−v1+w1,…,fj−vs+ws)fj=max(fj,fj−v0+w0,fj−v1+w1,…,fj−vs+ws) 就是说可以不选(选 0 个),选 1 个,选 2 个…选 s 个,进行三重遍历即可。

二、线性DP(转换过程是线性的一种dp)
1.数字三角形:每个点只能往左下或右下走,找到走到底层的路径总和最大的路径。
思路:由于当前点一定是由左上或右上的点走到的,因此当前点的路径最大值fx,y=max(fx−1,y,fx−1,y−1)+ax,y。
注意数组需初始化为负无穷,因为节点有可能是负值。最后需要遍历最后一层输出最大值。

2.最长上升子序列:求数列中严格单调上升的子序列的长度
思路:集合f[i]表示以i结尾的最长上升子序列长度,因此可以遍历从0到i-1的f[j]来更新f[i]:w[j]

3.最长上升子序列II:
思路:基于贪心:一个上升子序列,小的数一定比大的数更容易延伸,基于这一思想,开一个数组q[i],用来存放长度为i的上升子序列的末尾数字的最小值(边界q[0]是负无穷,保证对任意a[1]都能找到q[0]),对每个a[i],找到小于它的最大的q[j](由于数组q单调不减,因此可通过二分来加速查找,每次遍历只需Ologn的复杂度),然后将q[j+1] = a[i] (q[j+1]一定不比a[i]小,所以可将q[j+1]改成a[i])若j+1大于当前最大上升子序列长度,则更新最大上升子序列长度。

4.最长公共子序列:找出两个序列中最长的公共子序列
思路:用f[i][j]表示第一个序列第i个点和第二个序列第j个点之前的最长公共子序列的长度,集合可分为两种情况:1.a[i] != b[j] 则需往前找:f[i][j] = max(f[i-1][j] ,f[i][j-1] )2.a[i] == b[j] 则f[i][j] = f[i-1][j-1] + 1。

5.最短编辑距离:找出将字符串a变成字符串b的最小操作数
思路:从最后一次操作来看,一定是从倒数第二次操作增删改得到的,因此可以想到集合定义为f[i][j]表示从a[1] -a[i]变换到b[1]-b[j]所用的最少的操作数,f[i][j] 一共分为3种情况:1.改:前i-1和j-1都已经匹配,仅a[i] != b[j],则将a[i]改成b[j]即可,f[i][j] = f[i-1][j-1] + 1。2.增:前i个和前j-1个都已经匹配,a数组需要增加一个b[j]即匹配成功,则f[i][j] = f[i][j-1] + 1 3.删:前i-1个和前j个已经匹配,则需删除a[i]即可完成匹配,因此f[i][j] = f[i-1][j] + 1。三种情况去最小即可得到答案,注意边界:f[i][0] = i .f[0][j] = j。

三、区间DP:问题变成区间上的最值问题转化为区间内部的最值问题,最后收敛到边界条件f[i][i]
石子合并:每次合并相邻两堆石子,需要的代价为两堆石子的和,求合并完所有石子的最小代价。
思路:将区间l到r的石子合并首先必定需要l到r中石子的总重量,因此可预处理一个前缀和数组s表示石子的重量前缀和。然后区间代价的最小值可转化为从l到k任选一点,将l到k的石子合并的最小值加上从k到r合并的最小值再加上区间石子的总重(会递归到边界f[i][i] = w[i])。因此f[l][r] = min(f[l][r], f[l][k] + f[k][r] + s[r] - s[l-1])。

四、计数类DP:
整数划分:问一个整数恰好能由比他小的整数表示的最多个数。
思路:可将问题看成一个完全背包问题但集合属性由最大值变成了计数:从1到n可任意取无穷个数,保证总和是n的取法的个数,即f[i][j]:表示从前1-i中选,选的数之和正好为j的方案的个数。因此f[i][0]=1为边界条件(前i个都不选,显然和为0,也是一种方案)。
转移方程:可将集合分为不选i、选1个i、……、选k个i的情况,则f[i][j] = f[i-1][j] + f[i-1][j-i] + ……+f[i-1][j-ki],同理,f[i][j-i] = f[i-1][j-i] + f[i-1][j-2i]+…f[i-1][j-k*i]就是前一个式子第二项之后的所有方案,因此
f[i][j] = f[i-1][j] + f[i][j-i]
由于空间仅用到了一层数组,因此可以用滚动数组优化,注意更新j时需访问上一层的j和这一层的j-i因此j需从小到大遍历。即
for(int j = i ; j <= n; j++)
f[j] = (f[j]+f[j-i]) % mod;

五、数位统计DP:
计数问题:求一个区间中0-9出现的次数
思路:可以预处理出前n个数中0-9出现的次数,然后就可以用类似前缀和的思想解决此问题。
求0-n,数字x出现的的次数,可以从每一位来考虑,即x出现在第i位时,有多少个数(其实只有两种情况,即改变第i位之前到n能有多少个数第i位是x(乘上i的权重),加上第i位之后能有多少个数是x),逐一加和,这样就可以把暴力遍历的On复杂度降到Olog10n

六、状态压缩DP:
状态压缩其实是就是把n种状态压缩到二进制位上,就可以优化到Ologn。
1.蒙德里安的梦想:求将nm的空间用12的长方体塞满的方案数
思路:其实当把横着的长方体摆完,竖着的长方体就只有一种摆放方案,因此只用考虑长方体横着怎么摆即可,注意需要满足列上空着的空间是偶数。定义f[i][j]为前i-1列已经摆好,且从i-1伸到i列的方块个数是j的方案数,j用二进制表示第n个位置是否有方块的状态。此时已经知道了i-1列到i列的横着摆放的小方块,若再知道i-2列伸到第i-1列的方块数,就可以确定状态了,因此f[i][j] = f[i-1][k] + …这个k需要满足两个条件:1.横着的小方块不能和j重叠,即k&&j == 0 2.最后列上空着的空格应该是偶数,即观察k|j二进制中连续的0的个数。
可先预处理出一个状态z是否合法(是否满足连续的0是偶数个),再预处理出j对应的合法状态k(j&&k == 0, st[j|k] == 1),减少后续状态转移的计算量。最后的结果就是f[M+1][0]即前m列已经摆放完成,且没有伸到第m+1列的方案数。初始边界:f[0][0] = 1,表示一个小方块都没有的方案数为1。

2.最短Hamilton路径:给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
思路:假设现在已经不重不漏地走到第n-1号点了,那么应该如何优化路径长度?一个比较容易想到的方法是如果从第k个点到n-1的方案比当前的方案路径短,那么方案应该变成从当前是第k号点,没走过n-1号点,下一步走到第n-1号点,即f[i][j] = min(f[i][j], f[i ^ 1<

七、树形DP:问题结构是树
没有上司的舞会:显然当前节点有邀请和没邀请两种情况,邀请了则该节点的子节点都不能邀请,因此最大快乐值的状态表示每个节点应有两种状态即f[i][j],i表示当前节点编号,j表示是否邀请当前节点,f表示快乐度的最大值。显然f[i][0] = Σi的所有边max(f[e[i]][0],f[e[i]][1])。f[i][1] = (Σi的所有边 f[e[i]][0] )+w[i]。边界条件就是叶节点 f[叶][1] = w[叶],f[叶][0] = 0。

八、记忆化搜索:记住当前的状态,避免重复运算
滑雪:可以往上下左右4个方向走,只能往比自己低的区域走,求最长路径。
思路:显然当前点的状态值由上下左右4个点的值确定,注意处理时每个点可能会被遍历多次,而每个点的最长路径值f是不变的,因此碰到已经处理过的点时,应直接返回对应值,不需要再重复处理,也就是记忆化搜索。可以用dfs处理,对每个点进行dfs操作,返回最大值。dfs操作内容:先将距离设成1(不动就是1),然后查看上下左右4个方向能否移动,若能,则dfs那个点,然后距离等于当前的距离和dfs的结果+1中的最大值,最后返回移动距离。

你可能感兴趣的:(算法)