动态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)怎么打了

。。。。。
先咕咕,等停课了再说。

原文地址:https://www.cnblogs.com/Tyher/p/10025513.html