Dotcpp  >  编程教程  >  动态规划  >  动态DP实例讲解

动态DP实例讲解

点击打开在线编译器,边学边练

一、简介

有一类问题,它可以采用 DP 解决但是,如果我们加入区间查询单点修改甚至区间修改,普通DP望尘莫及。于是,动态DP就应运而生了。


二、例题

例题一:给定一个长度为 n 的序列,你需要维护两种操作:

①查询一个区间的最大子段和;

②单点修改(即将一个位置上的数改成另一个数)

 单点修改

Solution

首先,考虑这样一道题目:求出整个序列的最大子段和,没有修改

F01表示以 i 为结尾的区间的最大和,则不难得到状态转移:

F02

M01表示 [1,i] 的最大子段和,不难得到

M02

边界:F03

答案:M03


加入了单点修改以及区间查询之后,我们该怎么办呢?

观察一下我们的状态转移,可以发现——从F04转移到F05的转移式,仅仅与A01有关。这启发我们去给每个位置设定一个矩阵,并通过矩阵乘法来表示 f 的转移。

但是,转移式中含有max,不符合普通矩阵乘法的形式。不慌!我们可以大胆地重定义矩阵乘法 A ⋅ B = C:

重定义矩阵乘法

从而,我们就写出了 f 的转移与 i 之间的关系:

 f 的转移与 i 之间的关系

这样只能求出 f,而并不能求出我们所真正需要的 m 。所以,我们在矩阵中再加入一行一列来表示 m 的转移与f,i 的关系。

从而,我们得到了最终的式子:

最终的式子

对于一次形如 [L,R] 的查询,我们初始化MF,然后依次MF02将乘上 L , L+1 , ⋯   , R 处的矩阵即可。

暴力乘显然超时,考虑优化。注意到这个矩阵乘法是有结合率的,于是我们采用线段树来维护区间矩阵积,查询的时候直接用向量依次乘上 O(logn) 个矩阵即可。

对于一次单点修改,它只会导致一个矩阵的改变,于是在线段树上执行一次单点修改即可。

时间复杂度时间复杂度


例题二:给定一棵大小为 n 的树,每个节点有一个点权A04;q 次操作,每次单点修改或查询整棵树的最大独立集。

最大独立集

Solution

刚才我们所讨论的例 1 是序列上的问题。现在我们将它搬到了树上,显然无法凑效了。

依然先考虑不带修的情况。

F05表示,在 i 子树中的最大独立子集;F06表示节点 i 不能选,F07表示 i 必须选。

状态转移:

F07

不难发现,对一个节点的点权进行改变后,只会改变它以及它祖先的一些 f 值。但是,这棵树可能会退化成一条链,从而使得每次要修改的节点的数量很多。

考虑使用树链剖分来优化这一步骤。

令 i 的重儿子是 ws,G01G02,则不难得到

F03

与序列上的动态 dp 类似,可以设计一个广义矩阵乘法来完成转移:

广义矩阵乘法

若修改了 i 的点权,我们先在 i 处做单点修改,并不停地往上跳重链,通过线段树求出当前重链的矩阵乘积;同时,也要记得在跳到的链顶的父亲处做单点修改

时间复杂度 时间复杂度02


知识点标签:动态规划


本文固定URL:https://www.dotcpp.com/course/999

算法竞赛教程
第一章 算法基础
第二章 搜索算法
第三章 排序算法
第四章 字符串相关
第五章 数学相关
第六章 动态规划
第七章 数据结构
第八章 图论
第九章 计算几何
第十章 其他算法
Dotcpp在线编译      (登录可减少运行等待时间)