线段树优化dp学习笔记
到底是什么算法让我觉得两道题就足以让我写一篇学习笔记呢?
虽然两年半以前写过一道dp,正解的优化是单调队列,但是我拿线段树过了(卡着空间过的),所以那个dp并不能叫线段树优化dp。
这个算是最 “原汁原味” 线段树优化dp。
设 dpi,j 表示第 j 条路到第 i 条路全修的最大收益。(至于第 1 到 j−1 条路,那不是我们该考虑的,这就是dp的精髓)那么有转移:
dpi,j=⎩⎨⎧dpi−1,j+(lk≥j,rk=i∑pk)−ci(1≤y≤x<imaxdpx,y)+(lk=rk=i∑pk)−cij<ij=i其中:lk≥j,rk=i∑pk 和 lk=rk=i∑pk 分别表示右端点在 i 且被区间 [i,j] 覆盖的的比赛收益之和 和 左端点和右端点都在 i 的比赛收益之和。 那为何在 j<i 时只用考虑 lk≥j,rk=i 的比赛呢?因为 rk<i 的比赛都在 dpi−1,? 考虑过了,而 rk>i 的不是现在该考虑的。
但是,这题不可能这么简单,单是存状态和枚举状态就可以让我们爆炸了 先来优化空间:我们发现:1≤y≤x<imaxdpx,y 就是对于 i−1 的答案,于是第二个方程就可以表示成 ans+(lk=rk=i∑pk)−ci 。而我们发现第一个方程又只会从 i−1 来转移,那么我们可以考虑什么?滚动数组啊。那么空间就优化完了。
现在来优化时间。我们设每一个 dpi,j 都有一个待定值数组 tmp ,那么 dpi,j 一定由 tmp 里的某一个值转移而来。对于暴力来说,就是每次新枚举一个新的状态,都给他一个新的 tmp 数组,但是我们要优化,我们就不可能每次用 O(n) 给他一个新的 tmp 数组,我们应该继承上一个状态的 tmp 数组,并操作它使它变成当前状态的 tmp 数组。我们发现,每次转移,tmp 数组的 [1,i] 项都会加上 ci ,也会加上 lk≥j,rk=i∑pk 。这不就是区间操作吗?我们就可以用线段树来维护这个值。线段树里装的就是待定值数组 tmp 。而对于 i=j 的情况,因为本来只有 [1,i−1] 的下标里有值,而我们要用到 tmpi 的值,我们就可以给他赋值一个 ans ,正如同方程里的一样,而题目里让我们求最大的收益,那么线段树查询的就是区间 [1,i] 里的最大值。这样,我们就做完了。
有些细节比如如何记录 rk=i 的比赛会在代码里说明。
好题推荐:#
P2605 基站选址