动态规划进阶

\(tD/eD\) 描述动态规划,表示状态数为 \(n^t\),每个状态转移需要依赖之前的 \(n^e\) 个状态,一般复杂度为 \(O(n^{e+t})\)。但我们可以用一些特殊的优化降低复杂度。

单调队列

形如 \(dp_i=\max/\min~dp_j+val_{i,j}\),其中 \(val_{i,j}\) 可以拆开成关于 \(i~j\) 独立的形式。

P5665 [CSP-S2019] 划分

先考虑暴力,设 \(dp(i,j)\) 表示最后一段分割在区间 \([i,j)\) 上,那么最暴力的就是 \(O(n^3)\) 枚举,我们发现 \(dp(j,k) \to dp(i,j)\) 对于每一个 \(j\) 可选的 \(k\) 集应该是不断增大的,于是我们可以对于每一个 \(j\) 单调队列,复杂度 \(O(n^2)\)

其实我们可以配合贪心通过一些数学性质来解决这个问题或者打表,就像方差那题一样。对于这题显然越大的数,平方越大,也就是 \((a+b)^2 \ge a^2+b^2\),于是我们需要保证最后一段划分尽可能小,也就是这么多段尽可能接近或者说划分更多段。

于是我们设出 \(f_i\) 表示前 \(i\) 个数的答案,\(g_i\) 表示最后一段划分的和。这样只需要每次找到最大的 \(j\) 满足 $sum_{j+1,i} \ge g_j $,即 \(pre_i \ge g_j+pre_j\),相同变量在一侧,单调队列即可。注意 \(g_i\) 不一定大于 \(g_{i-1}\),只是要求大于前一段并不是大于上一个位置。

P1776 宝物筛选

多重背包模板题。多重背包最基本的写法是滚动数组设 \(f_j\) 表示前 \(i\) 个物品,体积为 \(j\) 的时候的最大价值,倒序枚举 \(j\) 即可。

\[f_j=\max\limits_{1\le cnt_i\le C_i}\{f_{j-cnt\times V_i}+cnt\times W_i\} \]

\(j=u+p\times V_i\),那么 \(j-cnt\times C_i=u+(p-cnt)\times C_i\),令 \(k=p-cnt \in [p-C_i,p-1]\),有

\[f_{u+p\times V_i}=\max\limits_{p-C_i\le k\le p-1}f_{u+k\times V_i}+(p-k)\times W_i \]

如果感觉不熟悉可以将这里的 \(p\)\(k\) 分别看成经典模型里面的 \(i\)\(j\)

于是我们只需要枚举 \(u\),然后维护一个决策点 \(k\) 单调递减,\(f_{u+k\times V_i}-k\times W_i\) 单调递增减的队列就行了。

斜率优化

形如 \(dp_i=\max/\min~dp_j+val_{i,j}\),其中 \(val_{i,j}\) 不能拆开成关于 \(i~j\) 独立的形式,但是可以拆成含 \(i~j\) 乘积项的形式。

最大化,斜率单调递减维护上凸壳。最小化,斜率单调递增维护下凸壳。时间复杂度 \(O(n)\)

很多斜率优化题其实都满足决策单调性,但不是所有的斜率优化都是决策单调性,因为如果插入的横坐标或查询的斜率不单调就不是决策单调性。

对于插入和查询都单调的题,可以直接用栈维护凸包,时间复杂度 \(O(n)\)P5468 [NOI2019] 回家路线

对于插入单调,查询不单调的,可以在凸包上面二分,时间复杂度 \(O(n\log n)\)P5785 [SDOI2012] 任务安排

对于插入和查询都不单调的,可以用 CDQ 分治,李超线段树,二进制分组,平衡树,四种方式来解决。其中后三种是通过动态维护凸包来做的,动态维护凸包模板题。第一种是依靠偏序配合朴素的斜率优化来处理的。时间复杂度 \(O(n\log n)\)\(O(n\log^2n)\)

感觉这种维护凸包,写成计算几何的形式是最好写不容易错的,就是自己定义几个点,向量类,以及它们之间的运算,用叉乘来判定凸包加点。P4027 [NOI2007] 货币兑换

P3628 [APIO2010] 特别行动队

\[dp_i=\max \left\{ dp_j+a(s_i-s_j)^2+b(s_i-sj)+c \right\} \]

\[\to dp_j+as_j^2-bs_j=(dp_i-as_i^2-bs_i-c)+2as_i \times s_j \]

这几项分别为纵坐标,截距,斜率和横坐标。发现斜率单调递减且要求最大化 dp,所以维护上凸壳即可。

P4027 [NOI2007] 货币兑换

不要被题目形式所迷惑,股票题的最佳策略是 allin。

所以必然是一天投入所有钱,另一天卖掉所有持有股票。然后重复多次这个过程。设 \(f_i\) 表示第 \(i\) 天最多持有的钱数。其中

\[f_i=\max(A_{i} \times cnt_{a}+B_{i} \times cnt_{b},f_{i-1}) \]

\(cnt_{a}=\dfrac{f_jR_j}{R_j\times A_j+B_j},cnt_{b}=\dfrac{f_{j}}{R_{j}\times A_{j}+B_{j}}\) 表示某天最多可以得到的 \(A,B\) 股票个数。

可以李超线段树优化,不过这里采用斜率优化。

因为式子中有 \(i\)\(j\) 的乘积项,所以用 CDQ 分治优化。

\[cnt_{b} = -\frac{A_{i}}{B_{i}}cnt_{a} + \frac{f_{i}}{B_{i}} \]

需要维护一个上凸壳,发现斜率和横坐标均不单调怎么办,可以用 \(\rm CDQ\) 分治解决。第一维选取时间维度,第二维选择横坐标,第三维选取斜率。根据状态转移需要,选择中序遍历。由于加点希望按横坐标,查询希望按斜率,故 $ (l,mid) $ 按照横坐标排序,$ (mid+1,r) $ 按照斜率排序。

带权二分

一篇不错的文章 可惜现在链接好像挂了。

应用场所: 给定若干个物品,要求恰好进行 \(k\) 次操作,最大化或最小化操作后的价值。其实要求 \(\le k\) 次的时候也行,后面会有例题。且代价关于操作次数的函数为凸函数。

特征: 1.一般来说,随着操作次数的增加,价值是单调变化的。 比如操作越多,答案越优。

2.如果不限制选的个数,那么很容易/很快可以求出最优方案。

做法: 虽然我们已知 \(f_n(x)\) 关于操作次数 \(x\) 是凸函数,但是我们并不知道 \(x=m\) 点的函数信息,我们可以通过用一条切线去切它不断获取函数信息。我们可以设每次操作的代价为 \(k\),然后算出来最后的最小价值就是 \(\min\{f(x)-kx\}\),这等价于用一条斜率为 \(k\)\(f(x)\) 相交得到的最小截距,显然是切线的时候截距取极值,这个时候我们就可以得到切点 \(x_0\) 处的函数值 \(f(x_0)\),我们需要不断逼近 \(m\),由于是凸函数斜率单调,所以我们直接对斜率二分即可。

你甚至可以带权二分套带权二分。

P1484 种树

这题就是操作次数 \(\le k\) 的情况,其实我们可以发现如果操作次数 $ \le k$ 的话那么就相当于没有限制,于是先对 \(c=0\) 跑一遍,如果\(g \le k\) 的话那就直接可以输出答案。如果 \(g > k\) 说明选取 \(>k\) 的时候更优,综合 $g \le k $,可以得出 \(g=k\) 的时候在此条件下最优。于是套上 wqs 二分即可。

P6246 [IOI2000] 邮局 加强版 加强版

小结论:满足四边形不等式的序列划分具有凸性。
有点逆天,如果只是二维决策单调性过不掉,于是先 wqs 二分处理掉 \(m\),然后发现剩下的一维居然还有决策单调性,于是再二分队列一下就行了。

P2619 [国家集训队] Tree I

wqs 二分不仅仅只能用来解决 DP,本题就是一个很好的例子。直接套用 wqs 二分后就解决了,不过多赘述,下面讨论一个 corner case。

注意在边权相同的时候,优先选白/黑是根据 check 函数中 \(\le\) 还是 \(\ge\) 来判断的。像我这种写法用 \(\le\) 是要黑边优先的,因为假设我们黑边优先无法达到 \(k\),白边优先可以超过 \(k\),那么在百边优先的情况下,我们会增大白边的边权,然后就会发现百边又不够了,但是不可逆回去,就错了。如果写的是黑边优先,那么我们会让白边权值变小,刚好可以使得原本黑白相等变成,白 \(<\) 黑,这样子就可以满足了。

其实我自己发现了一个最小化原则,就是 wqs 二分内部如果要 check \(\le k\) 的话,那么都是要最小化 \(cnt\) 的,优先选黑边可以最小化 \(cnt\)

ZROI2844.木芙蓉

有一棵 \(n\) 个节点的树,边上有边权,点上有点权。我们需要做恰好 \(k\) 次选择。每次选择一个节点和他的两条邻边,这里要求每条边只能被选择一次。可以获得的收益是所有被选择了至少 \(1\) 次的节点的点权之和减去所有被选择的边的边权和。请求出收益最大是多少,并构造方案。

答案关于 \(k\) 是凸的,所以可以用 wqs 二分来解决。

然后就变成了一个简单的树形 dp。首先儿子到父亲的约束只有那条边是否选择,于是可以直接设 \(f_{u,0/1}\) 表示 \(u-fa\) 这条边是否被选择。

转移的时候有点麻烦,因此可以开一个辅助数组 \(g_{i,0/1,0/1}\) 表示考虑了前 \(i\) 个儿子,\(u\) 有没有选,其 \(v-u\) 边被选择的奇偶性。

时间复杂度 \(O(n\log V)\)。这样子就解决了最优化的部分。

下面要进行 wqs 二分的构造方案环节,我们在 DP 的时候记录最多选几个,最少选几个。然后使用 \(\rm solve(u,0/1,k)\) 来递归求解,\(dp_{u,0/1}\) 的情况。

决策单调性

简述:若 \(dp_i\)\(dp_j\) 转移而来,则称 \(j\)\(i\) 的决策点,记为 \(p_i\)。如果 \(p_i\)\(i\) 的增大而单调不升或不减,则称 dp 满足决策单调性。一般有几种解决方法。

如何判断

有两种判断方式,一种是四边形不等式(一般用于抽象型贡献函数,比如区间满足某某要求的个数/对数之类的),另一种是函数角度(一般用于具体型贡献函数,有数学化的贡献式子,比如关于某个东西呈现二次函数之类的)。

四边形不等式

这里主要讲一维的,还是这个式子 \(dp_i=\min\{dp_j+w_{i,j}\}\),当单调队列与斜率优化都不能用的时候,我们就需要考虑四边形不等式优化。若对于任意 \(a\le b\le c\le d\) 都有:\(w(a,d)+w(b,c) \ge w(a,c)+w(b,d)\) 成立,则称 \(w\) 满足四边形不等式。从而 \(dp_i\) 有决策单调性。

上述式子有点抽象,不太好直接拿来套用,更常用的证明形式是对于任意 \(a<b\),有 \(w(a,b+1)+w(a+1,b) \ge w(a,b)+w(a+1,b+1)\)。简单来说就是相交优于包含,优于可以根据最大还是最小取大于或者小于。

证明:在 \(i\) 处有,\(f_{p_i}+val(p_i,i) \le f_j+val(j,i)\)。(其中 \(j<p_i\))。

我们要证的就是在 \(i'>i\) 处,同样满足 \(f_{p_i}+val(p_i,i') \le f_j+val(j,i')\)

等价于去证明:\(val(p_i,i')-val(p_i,i) \le val(j,i')-val(j,i)\)

又因为 \(val\) 满足四边形不等式,所以满足上述式子,故成立。

感性证明法

不过我觉得上述方法都过于机械化,赛场上不如使用一些感性观察法。

假设我们要最小化 \(f\),首先对于一个点 \(i\) 的决策点 \(p_i\) 有,

\[f_{p_i}+val(p_i,i) \le f_j+val(j,i) \]

现在要证明的就是 \(i\to i'\) 的时候,\(p_{i'}\ge p_i\),因此只需要考虑对于 \(j<p_i\) 不会成为 \(p_{i'}\)

\(i\) 点的时候,贡献区间 \([p_i,i]\subset [j,i]\),也就是说两个区间共用右端点,但是大区间延伸的更长。一般而言这种包含小区间的大区间贡献肯定是比小区间更大的(如果不满足,那决策单调性也就是很难成立了)\(\to val(p_i,i)\le val_(j,i)\)。也就是说 \(p_i\) 能成为决策点的原因是 \(f_j\) 没有小到 \(f_i-f_j\) 可以填补 \(val\) 之间的差距。

现在到了 \(i'>i\) 的位置,我们看证明的式子

\[f_{p_i}+val(p_i,i') \le f_j+val(j,i') \]

唯一区别就是两个贡献函数在起点不变的情况下终点同时延伸到了另一个更远的地方,还是上述道理,大区间的贡献函数一般是要大于其子区间的,而且一般来说同时增加相同一部分,大区间 \(val\) 的增长速度是要大于等于小区间 \(val\) 的增长速度的(因为大区间本来就有更多的部分来去与增加的一段区间相互作用产生贡献,当然如果每个位置贡献独立的话就是等于了)。所以我们 \(val\) 的差距不仅没有减小,反而增加了,本来就无法填补差距的 \(f\) 这下更没办法填补了。

看起来很长一段文字,理解上述思想的底层逻辑之后就可以丁真是否存在决策单调性了。

函数角度

考虑贡献函数,如果是直线直接斜率优化。

一般贡献函数可以通过平移得到,且二阶导非负或者非正。

贡献函数二阶导恒为非负,求最小值。或者贡献函数二阶导恒为非正,求最大值。那么就是二分队列了。

贡献函数二阶导恒为非负,求最大值;或者贡献函数二阶导恒为非正,求最小值。那么就是二分栈了。

分治

常用于 \(2D/1D\) 动态规划上,或者贡献难算的时候。

很好写,所以遇到非自转移的时候,一般都用分治解决。

自转移就是 \(f\to f\),非自转移就是 \(g\to f\) 或者 \(f_{i-1,j}\to f_{i,k}\)

实现方法

设目前待决策的范围是 \([l,r]\),决策点在 \([pl,pr]\),对于区间中点 \(mid\),暴力找到决策点 \(p_m\) 然后分治一下 \([l,m-1] \to [p_l,p_{m}]\) 还有 \([m+1,r] \to [p_m,p_r]\)

复杂度为 \(O(nk\log n)\)\(k\) 取决于你 DP 的层数。

P4360 [CEOI2004] 锯木厂选址

首先对于第二个工厂我们有,

\[f_{1,i}=\min(v_j+cost(j+1,i)) \]

不难证明 \(cost\) 函数满足四边形不等式,由于是分层转移,我们直接分治。
最后统计一下答案即可,\(ans=\min(f_{1,j}+cost(j+1,n))\)

还有一个随机化的解法,很有意思,把两个分治点算成二元组 \((x,y)\) 放到平面上,初始平面是 \(n\times n\) 的,随机出来一些点,看看那个更优,然后以最优秀的点为中心缩小平面和减少每次随机出来的点,直到平面足够小,就暴力把每一个点都扫描一遍,找到答案。

贡献难算

决策单调性的时候如果贡献难算,可以用分治来解决。

直接像莫队一样暴力移动端点,设单次移动端点的时间复杂度是 \(O(w)\) 的。那么可以证明总的时间复杂度就是 \(O(nw\log n)\)。一般 $w=\sqrt n $ 或 \(\log n\)

P5574 [CmdOI2019] 任务分配问题

不难验证贡献函数满足四边形不等式。使用分治解决决策单调性即可。

使用树状数组维护区间顺序对数目,移动端点是 \(O(\log n)\) 的。

总的时间复杂度就是 \(O(nk\log^2 n)\)

二分单调队列

决策点单调不降。

贡献函数二阶导恒为非负,求最小值。或者贡献函数二阶导恒为非正,求最大值。

具体实现方式就是维护一个队列里面储存着若干三元组,\((l,r,x)\) 表示 \([l,r]\) 这一段目前的最优决策点是 \(x\),初始放入 \((1,n,0)\)。每次取队首(绿笔部分)作为 \(i\) 的最优决策点,然后转移完后及时清理,使得队内待决策区间的范围是 \([i+1,n]\),接着我们需要把 \(i\) 作为决策点去更新队列里的决策点,由于我们此前加入了 \([0,i-1]\) 的决策点,所以当前加入 \(i\) 时根据决策单调性理论,修改的应该是末尾的一段 \([p,n]\) 范围内的决策区间。我们从队尾开始对于三元组进行判断,如果对于当前三元组的左端点有 \(i\) 优于 \(x\),那么意味着整个决策区间里都是 \(i\) 更优了,直接弹出队尾(就像图中红笔那样)。然后直到遇到一个区间(图中右数第三个区间)不满足上述要求,那么我们就判断一下该区间右端点是否使得 \(i\) 优于 \(x\),如果成立的话说明一段后缀是可以使得 \(i\) 更优的,我们直接二分一下就可以了,分界点就是蓝色矩形的左边界,然后我们直接把蓝色覆盖区间整体加入即可。

P1912 [NOI2009] 诗人小G

\(s_i=len_i+i\),列出状态转移方程 \(f_i=\min\left\{ f_j+\lvert s_i-s_j-1-L\rvert^p\right\}\),此时可以证明四边形不等式,也可以设出 \(f(x)=\lvert x-L \rvert^p\),去掉绝对值号,两段的二阶导显然非负而且是求最小值,故采用二分队列。

P3515 [POI2011] Lightning Conductor

首先肯定需要正反各做一遍,这里讨论正着做。

\(f_i=\max\left\{a_j+\sqrt{i-j}\right\}\),我们对于贡献函数 \(\sqrt{x}\) 求导发现二阶导小于等于 \(0\),且函数求最大值,所以可以二分单调队列。其实这个决策单调性也可以通过对于四个点 \(i<j<k<z\),若 \(val(i,k)<val(j,k)\),则必然有 \(val(i,z)<val(j,z)\),迅速发现决策单调性。

本题其实不用二分,因为贡献式子特殊,我们可以在 \(O(1)\) 时间内找到转折点。

P10538 [APIO2024] 星际列车

图上最短路形式的转移是没有前途的,上述本质是对于图上每个点的考虑,因为点本身还需要时间维护作为辅助,这样子直接就炸了。

考虑对于边进行 dp,这样子边是自带唯一时间区间信息的,设 \(dp_i\) 表示通过边 \(i\) 到达 \(Y_i\) 的最小代价。我们需要建立起点和终点两个虚点以得到两条起始和结束的终边。考虑暴力 dp,

\[f_i=C_i+\min\limits_{Y_j=X_i,B_j\le A_i}f_j+T_{X_i}\times w(B_j+1,A_i-1) \]

其中 \(w(l,r)=\sum\limits_i [~[l_i,r_i]\subset [l,r]~]\),这是一个在线二维数点的形式,可以考虑用主席树维护。

第一反应肯定是开 \(n\) 颗动态开点线段树优化 dp,虽然查询很快,但是注意你每次更新的时候需要更新 \(n\) 次,这直接就爆了,不过这个东西是可以对于度数根号分治的。对于大度点用线段树维护,小度点暴力转移,复杂度可以做到 \(O(\dfrac{n^2}{B}\log n+nB)\)。取 \(B=\sqrt{n\log n}\) 的时候最优。注意这个小度点 \(w\) 函数的求解不能用主席树,不然会多一个 \(\log\),应该平衡一下采用 \(O(\sqrt n)\) 修改,\(O(1)\) 查询。

当然也可以直接用分块维护二维平面,对于横坐标分块使得可以快速更新,块内对于纵坐标排序离散化维护。这样子就直接是单根的了。

其实这个 dp 是有决策单调性的,用我上文中的思考方式可以很快看出来。

于是我们直接对于每个点都维护一个二分队列,执行决策单调性就行了,在线二维数点用主席树完成。这样子时间复杂度为 \(O(n\log^2 n)\) 的。发现瓶颈在于每次都用 \(\log\) 的时间去求代价,我们可以只计算一次代价提前求出超越时间以此做到单 \(\log\) 的。

二分单调栈

非传统决策单调性,之所以是非传统,是因为每个决策点只会被它更靠前的决策点反超。

在贡献函数二阶导恒为非负,求最大值;或者贡献函数二阶导恒为非正,求最小值的时候使用。

我们可以用单调栈来维护,考虑加入一个新的决策点,如果它已经弱于栈顶了,那么根据上述性质它已经被反超了就没有后续产生贡献的可能了直接出去,但是对比一下单调队列,单调队列是可能被后续的点所取代,所以我们可以在加入后续点的时候来计算能取代哪些点,但是这个单调栈里的是可能被前面点取代,我们不可能每次都遍历一遍栈来看看哪些可以取代前面的吧,所以每次加入一个新决策点的时候,我们都要提前计算一个它前面一个点何时反超自己,每次决策的时候先看当前时间是否已经达到栈顶被反超的时间,如果是的就直接弹出继续判断下一个就行了。

P5504 [JSOI2011] 柠檬

贪心一下,每个选取区间左右端点应该都是该区间所要钦定的贡献颜色,于是有

\[f_i=\max\limits_{c_i=c_j}\{f_{j-1}+c_i\times(sum_i-sum_j+1)^2\} \]

我们要求最大化且贡献函数二阶导非负,所以可以考虑单调栈。

优化背包

在体积种类很少的时候可以使用,我们将体积相同的物品合并,对于代价从小到大排序,显然选择是一段前缀。

\(f_{i,j}\) 表示前 \(i\) 种体积用了 \(j\) 体积可以得到的最大代价。设 \(p_j\) 表示 \(f_{i,j}\) 的最优转移是选择前缀多少个物品。那么把 \(j\) 按照 \(\bmod V_i\) 分组,组内具有决策单调性。

证明可以考虑排序之后前缀和的上凸性。

LOJ6039. 「雅礼集训 2017 Day5」珠宝 /「NAIPC2016」Jewel Thief

上述结论的模板题。

对于同一体积排序之后算出前缀和 \(s_{i,j}\)。枚举模 \(v_i\) 的余数 \(z\),单次求解的值域上界就是 \(\dfrac{k-z}{v_i}\),总的次数就是 \(v_i\times \dfrac{K}{v_i}=K\),故暴力枚举余数的复杂度是正确的。

使用分治法求解决策单调性即可。时间复杂度 \(O(n\log n+cK\log K)\)

QOJ9119. Graph Weighting

不难发现一个点双内部的权值是相同的(注意不是边双,反例是两个环交于一个点)。

然后就变成了一个背包问题,由于体积总和是点数为 \(n\)。根据经典结论,一共有 \(O(\sqrt n)\) 种不同的体积。

利用上述结论对于每种体积决策单调性分治求解即可。时间复杂度 \(O(n\sqrt n\log n)\)

二维四边形不等式的应用

对于状态转移方程 \(F_{i,j}=\min \left\{ F_{i,k}+F_{k+1,j}+w(i,j) \right\}\),如果满足

  1. \(w\) 满足四边形不等式。
  2. 对于任意 \(a\le b \le c \le d\),有 \(w(a,d) \ge w(b,c)\)

那么可得 \(F\) 也满足四边形不等式。
如果 \(F\) 满足四边形不等式,则对于决策点 \(P\) 满足,\(P_{i,j-1} \le P_{i,j} \le P_{i+1,j}\)
在此范围内枚举决策点 \(k\) 即可,for(int k=p[l][r-1];k<=p[l+1][r];k++)

理解一下,我们固定 \(i\),发现是 \(j\) 处的一个寻找最优 \(k\) 的过程,满足决策单调性,所以 \(P_{i,j-1}\le P_{i,j}\)。固定 \(j\) 同理。

P1880 [NOI1995] 石子合并

满足上述式子,直接四边形不等式即可。

P4767 [IOI2000] 邮局 加强版

\(f_{i,j}\) 表示前 \(i\) 个村庄,设置了 \(j\) 个邮局的最小距离和。
\(f_{i,j}=\min\{f_{k,j-1}+w(k+1,j)\}\)
其中 \(w_{l,r}=w_{l,r-1}+X_r-X_{\lfloor\frac{l+r}{2}\rfloor}\),或者前缀和计算。
有点抽象,我以为只有石子合并那个式子才可以二维四边形优化,原来这个式子也可以啊。\(P_{i,j-1}\le P_{i,j}\le P_{i+1,j}\)

Slope Trick

维护一个图像为凸函数的 \(dp\) 信息。一般是 \(dp_{i,j}\),我们发现对于每一个 \(i\),将 \(j\) 作为横坐标,\(dp_i\) 作为纵坐标,形成的是一个凸函数。对于每个 \(i\) 维护一个 \(dp_i\) 关于 \(j\) 的图象。

每次更新肯定是由 \(i'\) 转移到 \(i\),直接在 \(i'\) 的图象上修改得到 \(i\) 的图象。对于每个 \(j\) 显然加入的是一个与 \(j\) 相关的函数。由于一次凸函数的性质只需记录最左端的直线信息以及拐点即可。一个拐点相当于斜率变化 \(1\),如果要变化很大就加入多个相同点。要求:图象为连续的分段一次凸函数。注意:一般绝对值产生贡献的动态规划题目都可以往这方面想。

维护方法:

  1. 合并:最左端一次函数斜率和截距相加,拐点集合取并集。

  2. 寻找最值,找到斜率为 \(0\) 的一段,用大根堆 \(R\) 维护左边分段点,用小根堆 \(L\) 维护右边分段点。两个堆顶即为最大值的左右端点。

  3. 加入一次函数:\(y=x\) ,将 \(R\) 堆顶放入 \(L\) 中。如果加入一次函数斜率较大,可以将修改集合定义为每个位置斜率变化量即差分,修改 \(k\)\(b\) 即可。

  4. \(F\) 函数取前缀 \(\max\) 记为 \(G\), 则 \(G\) 就是 \(F\) 的集合去掉 \(R\)

  5. 平移和翻转:维护 \(k\)\(b\) 变化,分段点打平移或者翻转标记。

CF713C Sonya and Problem Wihtout a Legend

小技巧:令 \(a_{i} \gets a_{i}-i\) 就可以转化为不降了。设 \(f_{i,j}\) 表示第 \(i\) 个数调整为 \(j\) 的最小代价。

\[f_{i,j}=\min_{k=1}^{j} f_{i-1,k}+|a_{i}-j| \]

\(F_{i}(x)=f_{i,x}\) , \(G_{i}(x)=\min_{k=1}^x F_{i}(k)\),可得

\[F_{i}(x)=G_{i-1}(x)+|a_i-x| \]

\(F\)\(G\) 均为凸函数,本题转移和答案都与 \(G\) 有关,这样只需要维护 \(G\) 即可。由上述性质 \(4\) 可得 \(G\) 的最后一段是平的,于是我们只需要维护 \(L\) 堆即可。

加入绝对值函数相当于在 \(a_i\) 处斜率变化 \(2\),于是加入 \(\left\{~a_i,a_i~\right\}\)。观察一下贡献 $x \le a_i $ 部分加上了一次函数 \(a_{i}-x\) 相当于斜率都减 \(1\), \(x \ge a_{i}\) 部分加上了 \(x-a_i\) 相当于斜率加 1。

\(a_i \le L.top()\) 时候 \(L.top()\) 决策点因为斜率增加 \(1\),所以不是平的了,于是 \(L.pop()\) 平线段抬升代价为 \(x=L.top()\) (未 pop 前的值)带入 \(x-a_i\)

\(a_i>L.top()\) 时候第一个 \(a_i\)成为最优决策点,第二个 \(a_i\) 不优所以不插入。而且注意到加入的函数 \(\lvert x-a_i \rvert\) 函数值为 \(0\) 的点一定在平线段的正下方,所以无代价。

CF372C Watching Fireworks is Fun

很显然的 Slope trick 形式,取区间 \(\min\),加绝对值一次函数。

不过先要简化一下,可以先把答案累加 \(\sum b_i\),然后把最小化 \(-\sum\lvert x-a_i\rvert\) 变成最大化 \(\sum\lvert x-a_i\rvert\)

P3642 [APIO2016] 烟火表演

\(F_u(x)\) 表示以 \(u\) 为根的子树中到达时间为 \(x\) 的代价,则有

\[F_u(x)=\sum F_v(k)+\lvert x-k-l_{v \to u}\rvert \]

我们发现了绝对值函数,联系之前 \(dp\) 优化策略可以联想到凸函数优化,数学归纳法可证 \(F_u(x)\) 为下凸函数。

凸优化是需要通过一个堆维护点集,由于需要子节点的向上合并所以可以用到可并堆。

这题转移方程形式和 CF713C 不一样,那题的贡献函数与 \(k\) 无关,\(k\) 可以直接调整至最优,所以可以直接处理出前缀最大值。而这道题贡献函数与 \(k\) 有关,所以我们应该思考对于父节点的 \(x\) 子节点的什么 \(k\) 转移过来才是最优的。

还是记子节点斜率为 \(0\) 那一段左右端点为 \(L\)\(R\)\(x\) 的关系。

\(x \le L\) 时候 \(f_u(x)=f(x-deta)+deta+l_{v \to v}\),考虑 \(f(x-deta)+deta\) 有两种思考方案

  1. 分析函数性质 \(f(x)\) 斜率 \(\le-1\),所以 \(deta=0\) 的时候 \(f(x-deta)+deta\) 取最小值

  2. 思考实际意义,\(deta=0\) 的时候相当于在 \(u \to v\) 这条边上直接修改边权,如果 \(deta>0\) 相当于对于 \(v\) 的每一个子节点进行修改,因为 \(v\) 的子节点个数 $ \ge 1$ 需要修改更多的边,所以对于子节点的边修改显然是需要更大代价。

于是就有 \(x \le L\) 的时候 \(f_u(x)=f_v(x)+ l_{u \to v}\)

其他情况也是类似思考方式。

可以得到

\[ f_u(x) = \begin{cases} f_v(x)+l & x \le L \\ f_v(L)+l-(x-L) & L < x \le L+l \\ f_v(L) & L+l < x \le R+l \\ f_v(R)+x-R-l & R< x \end{cases}\]

思考一下如何转移,第一段是向上平移,第二段是加入一条一次函数,第三段是向右边平移。第四段是将最后的斜率改为 \(1\),原因是由 \(f(R)\) 这个值加上一次函数得到的所以斜率为 \(1\)。其实第三段我们可以不用管因为在处理第二段的时候就相当于将第三段平移过了。可并堆维护即可。

这种复杂函数图象维护可以自己画图感受一下更好。网上有一些这种凸函数维护操作的模板方法,但是实际运用起来却不可照搬,像这题的处理操作就很灵活,和我之前看到的处理模板方法不太一样,每一题需要思考符合题意的维护方式,仔细画图研究,大胆维护。

AGC049E Increment Decrement

一个最优化套计数的好题。

先思考最优化,也就是给定唯一 \(b\) 序列的情况。将所有数加成 \(b_{i,j}\) 看成将 \(b_{i,j}\) 削成 \(0\)。于是下文的所有操作都反过来,加法变减法,减法变加法。

首先这个区间加显然是无意义的,首先单纯加会使得我们背离变成 \(0\) 的目标,其次如果区间加和区间减复合的话,我们发现如果一个区间减被 \(k\) 个区间加覆盖的话,那么会变成 \(k+1\) 个区间减,反正代价都是 \((k+1)C\),那么可以被 \((k+1)\) 个区间减平替。

设这个序列为 \(\{a_i\}\),如果只有区间操作的话,那么根据 P1969 [NOIP 2013 提高组] 积木大赛 的结论,我们的代价是 \(C\sum\limits_{i=1}^n \max(0,a_i-a_{i-1})\)

不过我们还有单点加减 \(1\) 的操作,这个可以通过设计 DP 来解决。设 \(f_{i,j}\) 表示调整之后 \(a'_i=j\) 的最小代价。

注意到只有 \(j\in \{a_i\}\)\(j\) 才是有效值,因为我们 \(\pm 1\) 的目的是将一些值调整成一样的,便于一起减。

\[f_{i,j}=|a_i-j|+\min\limits_{k}\{f_{i-1,k}+C\max(0,j-k)\} \]

暴力转移是 \(O(n^3)\) 的。注意到这是很显然的 slope trick 的结构,于是可以维护这个下凸函数。

先进行对于 \(j<k\) 的转移,就是一个往后的 chkmin 的形式,也就是要把前缀的斜率小于 \(0\) 的部分推平。对于 \(j>k\) 的转移,可以发现是把后缀斜率大于 \(C\) 的部分推平。由于涉及删点,所以考虑用 multiset 来维护。

初始加入 \(C\)\(0\) 表示 \(a_0\) 的变化代价,第一次的时候可以发现没有大于 \(C\) 的斜率和 小于 \(0\) 的斜率可以按照常用的加入绝对值函数的方式塞入两个 \(a_i\)。后面的时候可以发现每次都恰好有一条斜率为 \(-1\) 的直线和斜率为 \(C+1\) 的直线,直接弹出即可。同时维护斜率为 \(0\) 的那一段的高度就是最后的答案,每次动态维护抬升代价即可。由于每次最小点对应的就是斜率为 \(0\) 的线的转折点,所以每次累加 \(a_i-\rm mn\) 即可。时间复杂度 \(O(n\log n)\)

现在考虑如何对于所有序列计算答案,使用考虑贡献法。发现贡献出现在 \(a_i\)\(\rm mn\) 上面。对于 \(a_i\) 是固定贡献,\(a_i\times m^{n-1}\)。但是 \(\rm mn\) 就需要我们进行精细计算了,直接做不好做,涉及相对大小的问题可以通过二分转 \(01\) 来计算贡献。

枚举阈值 \(v\),将 \(\ge v\) 的值设置为 \(1\)\(v\) 能产生贡献当且仅当 multiset 中有 \(C+2\)\(1\),我们需要统计这个方案数。

\(g_{i,j}\) 表示 \(i\) 轮之后 multiset 内部有 \(j\)\(1\) 的方案数。每次模拟上述 slope trick 中的更新集合的方式来转移 DP 数组即可。时间复杂度 \(O(n^3m)\)

动态 dp

一般是对于简单树上 dp,带上了修改操作。以下面一题作为例题。

P4719 【模板】"动态 DP"&动态树分治

首先是不带修改,\(f_{u,0/1}\) 分别表示选或者不选 \(u\) 点。

\[f_{u,0}=\sum\limits_{v} \max\{f_{v,0},f_{v,1}\} \]

\[f_{u,1}=\sum f_{v,0}+a_u \]

我们发现对于每次修改一个点改的只是一条链上面的结果,于是我们可以采用重链剖分。如果能快速在重链上面转移,那么只需要跳 \(\log\) 次重链就可以修改出结果了。

考虑使用支持快速对于重链修改的方程,增加一个表示 \(g_{u,0/1}\) 分别表示轻儿子可选可不选或者全部不选。

\[f_{u,0}=g_{u,0}+\max(f_{son,0},f_{son,1}) \]

\[f_{u,1}=g_{u,1}+a_u+f_{son,0} \]

\(g_{u,1} \gets g_{u,1}+a_u\),于是有 \(f_{u,1}=g_{u,1}+f_{son,0}\)

\[\begin{bmatrix}g_{u,0}&g_{u,0} \\g_{u,1}&-\infty \end{bmatrix}\begin{bmatrix}f_{son,0} \\f_{son,1} \end{bmatrix}=\begin{bmatrix} f_{u,0} \\f_{u,1} \end{bmatrix}\]

这是一个方便在重链上转移的方程,因为对于重链上面的点只要它们的轻儿子 \(f\) 值没有发生变化,那么矩阵就不变了。对于重链上某点其 \(f\) 数组只需要链末尾到该点一路上 \(g\) 数组的乘积,乘以链末尾的 \(f\) 数组就行了。轻重链交界处,我们需要单独修改一次 \(g\) 就行了,这种情况只会出现 \(\log\) 次。

矩阵具有结合律,因此可以直接用线段树维护区间矩阵积就行了。

同时,我们发现重链链尾也就是叶子节点上 \(f\)\(g\) 相等,所以后续维护不需要维护 \(f\) 了, 直接用 \(g\) 往上乘就行了。

时间复杂度 \(O(n\log^2 n)\),常数很大。

P4751 【模板】"动态DP"&动态树分治(加强版)

需要使用全局平衡二叉树,全局平衡二叉树就是 LCT 的静态版本。

使用重链剖分之后,对于每个重链开一个平衡树来维护。对于每条重链建立 BST 的过程就是你设一个点的权值为轻子树大小之和,每次找到带权中间分裂开建立左右儿子就行了。

更新的时候,到每个节点都要 pushup 更新一下自己。如果 \(fa_u\) 是同一平衡树内的点就暴力往上跳。如果是在轻重链交界处的话,就直接修改一下上方矩阵。

处理链信息的时候比树剖快,这个是单 \(\log\) 的。

P8820 [CSP-S 2022] 数据传输

\(dp_{i,0/1}\) 分别表示到 \(i\) 自己或者最小的儿子的最小代价。
分析可知 \(k=1/2\) 的时候走到儿子不优。

  • \(k=1~~dp_i=dp_{i-1}+val_i\)

  • \(k=2~~dp_i=\min\{dp_{i-2},dp_{i-1}\}+val_i\)

  • \(k=3~~ \begin{cases} dp_{i,0}=\min\{dp_{i-3,0},dp_{i-2,0/1},dp_{i-1,0/1}\}+val_i \\dp_{i,1}=\min\{dp_{i-2,0},dp_{i-1,0/1},dp_{i,0}\}+w_i \end{cases} \)

对于 \(k=1/2\),构造矩阵

\[\begin{bmatrix} val_i&\infty&\infty \\ \infty&0&\infty \\\infty&\infty&0 \end{bmatrix}\begin{bmatrix} f_{i-1}\\0\\0 \end{bmatrix}=\begin{bmatrix}f_i\\0\\0\end{bmatrix}\]

\[\begin{bmatrix} val_i&val_i&\infty \\0&\infty&\infty \\\infty&\infty&0 \end{bmatrix}\begin{bmatrix} f_{i-1}\\f_{i-2}\\0 \end{bmatrix}=\begin{bmatrix}f_i\\f_{i-1}\\0\end{bmatrix}\]

前两个可以轻松用用目前形式矩阵解决,\(k=3\) 的时候 \(5^3\) 可承受不了。
我们发现有些状态是重复的,比如从 \(i\) 跳到 \(i+1\),从 \(i-1\) 或者 \(i_v\) 开始跳距离都是一样的。也就是说在往后跳无论到哪个点,这两个的距离都是一样的,所以可以放在一个状态里。
于是我们考虑优化一下化简一下状态数,可以发现约束条件只与距离有关,所以我们可以将距离放入状态。
于是设 \(dp_{i,0/1/2}\) 表示跳到离 \(i\) 距离为 \(0/1/2\) 的最小值。

\[\begin{cases}dp_{i,0}=\min\{dp_{i-1,0},dp_{i-1,1},dp_{i-1,2}\}+val_i \\dp_{i,1}=\min\{dp_{i-1,0},dp_{i-1,1}+w_i,dp_{i-1,2}+val_i+w_i\} \\dp_{i,2}=dp_{i-1,1} \end{cases}\]

这样子就解决了。我们继续列出矩阵

\[\begin{bmatrix} val_i&val_i&val_i \\0&val_i&val_i+w_i \\\infty&0&\infty \end{bmatrix} \begin{bmatrix} f_{i-1,0}\\f_{i-1,1}\\f_{i-1,2} \end{bmatrix}=~\begin{bmatrix}f_{i,0}\\f_{i,1}\\f_{i,2} \end{bmatrix}\]

然后我们预处理出向上和向下的转移矩阵幂即可。注意矩阵没有交换律,所以必须分向上和向下解决。
注意题目中说了 \(s_i \neq t_i\),否则需要特判起终点重合。

DP 套 DP

当我们发现 DP 的某一维度是一些状态,且状态数很少的时候可以通过 dp 求出所有可能的状态,对于这些可能的状态作为外层 DP 的某一维度进行 dp。

P4590 [TJOI2018] 游园会

可以设 \(dp_{i,s,j}\) 表示考虑到了前 \(i\) 位,与奖章串的匹配情况为 \(s\),目前结尾与 NOI 三个字母的匹配长度为 \(j\)。可以发现 \(s\) 维度其实是 LCS 求解中的 \(f_{i,j}\) 函数值,固定 \(i\) 之后,状态是那一行的 \(f\) 值。有一个性质就是 \(f_{i,j-1}+1\ge f_{i,j}\),所以我们直接记录差分值为 \(0/1\) 即可。这样子状态数就是 \(2^k\) 了。每次加入一个数之后,就将这个状态解码成 \(f\) 数组,dp 一下再编码回状态就行了。很多状态都是无用的,所以跑不满。

P8352 [SDOI/SXOI2022] 小 N 的独立集

考虑树上独立集的求解,我们需要记录当前点是否选择,然后 dp 值是最大权值。现在由于加了一个要求就是对应权值的方案数,于是我们将之前的是否选择,改为选择得到的最大权值和不选得到的最大权值。设 \(dp_{u,f_1,f_2}\) 表示在 \(u\) 子树内,不选 \(u\) 得到的最大独立集为 \(f_1\),选 \(u\) 得到的最大独立集为 \(f_2\)

发现后两维的状态是一个树上独立集的 dp,直接做复杂度爆了。考虑简化状态,可以发现如果将 \(f_2\) 的定义改为不强制\(u\),那么可以得到 \(f_2-f_1\le k\)

于是直接把第二维度改为 \(f_2-f_1\) 就可以大幅度缩小状态了。

posted @ 2024-02-11 21:51  Mirasycle  阅读(89)  评论(0)    收藏  举报