动态dp学习笔记
\(noip\)考了,赶紧补一发。
不得不说网上的题解还是不错的ljq的代码吼啊
- 一开始看的博客
- 模板
其实我感觉看博客不如看别人优秀的代码来的快- 朴素\(dp\)的想法就是\(f_{i,01}\)表示当前点\(i\)选还是不选。
- 而动态\(dp\)的思想就是,把\(dp\)方程写成矩阵乘法的形式,然后用数据结构来维护区间矩阵乘积。
首先是树链剖分的思想
- 讲一讲自己的感想
- 首先我们为了使得\(f\)带修改,引入了树链剖分套线段树维护矩阵进行\(dp\)。
- 但是这样我们是无法直接维护\(f\)的,因为剖分的思想是重链不会超过\(log\)次划分。
- 假设我们现在可以一次性跳完整个重链,那么问题就变成了是否可以从重链顶跳到上一个重链
- 于是我们引入新数组\(g\),表示不算当前重链的所有贡献。
- 那么我们只需要维护\(g\),每次修改就直接把整个重链的\(g\)全部\(\prod\)起来,然后修改父亲的\(f\)。
- 此时我们发现,不是每个地方的\(f\)都是有意义的。
- 首先我们知道每个位置的\(g\),然后用线段树维护他。
- 维护\(g\)的原因是我们可以通过线段树优化\(dp\)转移使得在\(log\)的时间内得到一段的\(g\)卷积。
- 所以我们只知道每个链顶的\(f\)值,而并不关心每个点的\(f\)。
- 那么问题就简单多了,对于每次修改,我们只需要修改当前点的\(g\),然后\(\prod\)到链顶的\(f\),再用当前的\(f\)更新父亲的\(g\),重复这个操作。
- 实际上我们又给出了\(f\)的新定义,即\(f=\prod g\).
- 总结一下就是:
- 个人认为动态\(dp\)的思想在于整体考虑一整个重链的\(dp\)转移。
- 然后再把这个重链的\(dp\)转移贡献给链顶父亲
- 那么在实际设计状态的时候,形如\(g_{i,j}\)表示\(i\)点不考虑重儿子的\(dp\)值。
- 然后根据状态转移方程一次性考虑一整个重链
- 这样的话,我们在修改操作的时候,就只需要考虑链与链之间的关系了。
- 至此,我们得到了一个\(O(4*q*log^2n)\)的做法。
但是这样还是不够优秀
- 我们选择更加优秀的\(lct\)维护矩阵信息。
- 优秀博客
- 不会lct来这里moflash
- 此时的\(f\)含义为\(splay\)整个的\(g\)卷积,\(g\)表示虚树\(dp\)和。
- 那么\(splay\)就维护了整个实链的\(dp\)值。
- 重点说\(access\)……
- 转到根,换儿子,更新信息,前操作点切换为轻边所指的父亲,转1。
- 因为除此之外全是模板。
- 假设当前实链顶端结点是\(y\),它爹是\(x\),我们要做的就是把\(x\)在\(splay\)上的右儿子设置为\(y\)。这对于我们维护的虚子树信息而言有两个影响:
- 原来\(x\)的右儿子变成了虚的,相当于多了一棵虚子树。
- \(y\)由虚的变成了实的,相当于少了一棵虚子树。
- 另外这个\(LCT\)有点特别:它既不\(L\)也不\(C\),所以我们不需要翻转标记,不需要\(pushdown\),不需要\(makeroot\)什么什么的。
- 至此,我们得到了一个\(O(4*q*logn)\)的做法。
- 至于为什么复杂度更加优秀,是因为树剖要一条链一条链往上,一边改一边查,而\(LCT\)只要简单粗暴地\(access\)一下即可。
- 在区间问题中,树剖加线段树是将链进行分治从而提取了很多段区间分别分治,但是\(lct\)和\(splay\)的优势在于可以动态快速提取出整个区间。
- \(lct\)相比于树剖,其实是避开了链分治的过程,也就省去了一个\(log\)的时间复杂度。
- 至于实现过程,我们一开始不需要建所有的\(splay\),可以认为一个\(splay\)就只有一个点本身,也就是所有的边都是虚的。
但是问题在于,我好像忘了\(lct\)怎么打了
。。。。。
先咕咕,等停课了再说。