WQS二分 学习笔记
问题引入
前置问题:把长度为 \(n\) 的正整数序列分为若干段,一段代价为这段和的平方加一个常数 \(c\),求最小代价。
设 \(f_i\) 表示考虑前 \(i\) 个数且最后一段结尾为 \(i\) 的代价,答案为 \(f_n\),\(f_i=\max_{j=0}^{i-1}\{f_j+(s_i-s_j)^2+c\}\),可以斜率优化,时间复杂度 \(O(n)\)。
把长度为 \(n\) 的正整数序列分为 \(k\) 段,一段的代价是这段和的平方,求最小代价。
设 \(f_{i,j}\) 表示前 \(i\) 个数分为 \(j\) 段,且最后一段结尾为 \(i\) 的代价,答案为 \(f_{n,k}\),同样可以斜率优化做到复杂度 \(O(nk)\),在 \(k\) 较小的时候已经可以通过了,但是 \(k\) 较大的时候,我们需要更优的算法。
算法思路
发现若不限制段数,这个问题的答案一定是把每个数都分为一段。我们可以通过修改分段的代价,给每一段加上一个额外的代价 \(c\)。
即 \(f'_{i,j}=\max_{k=0}^{i-1}{f'_{k,j-1}+(s_i-s_k)^2+c}\),也就是说 \(f'_{i,j}=f_{i,j}+jc\)。
直观感觉,\(c\) 越大,分出的段数就越少。我们可以试着通过二分这个 \(c\) 使得 \(f'_{n,x}\) 取得最小值时,\(x\) 恰好等于 \(k\)。这个时候问题等价于前置问题,算出 \(f'_{n}\) 就是 \(f'_{n,k}\),然后 \(f_{n,k}=f'_{n,k}-kc\)。
WQS 二分又叫 DP 凸优化,可以从几何的角度理解这个算法。
一种理解方式:考虑在平面上有 \(k\) 个点 \((i,f_{n,i})(1\leq i \leq k)\) ,同时相应的有 \((i,f'_{n,i}=f_{n,i}+ic)\) 这 \(k\) 个点,也就是说以 \(c\) 的斜率将原来的 \(k\) 个点拉伸得到一个新的图形。
根据题意观察可以发现一个事实:随着 \(c\) 的增大,这 \(k\) 个点中纵坐标最小的点的横坐标在不断减小,所以感性理解认为这 \(k\) 个点构成下凸壳。
所以我们二分 \(c\) ,直到拉伸得到的图形纵坐标最小的点横坐标为 \(k\) 即可。
如下图,从下向上第 \(2,3\) 个图形都是由原始的凸壳 \(1\) 以不同的 \(c\) 拉伸得到。红色的点横坐标为 \(k\),在第二个图形里面时为求得的 \(x\)。
另一种理解方式:用斜率为 \(-c\) 的直线交 \((i,f_{n,i})\) 构成的凸壳,设此时的直线为 \(y=-cx+b\),交点为 \((x,f_{n,x})\),所以 \(f_{n,x}=-cx+b\),因此 \(b=f_{n,x}+cx=f'_{n,x}\)。由于满足 \(f'_{n,x}\) ,即 \(b\) 最小,其实就是要求 \(y=-cx+b\) 和凸壳相切。
发现斜率 \(-c\) 越小,切到的点横坐标越小,我们要二分调整切线的斜率 \(-c\) 使得切点横坐标为 \(k\),也就是 \(b=f'_{n,x}\) 最小时 \(x=k\)。
如图,点 \(C\) 为 \((k,f_{n,k})\),红色的直线即为所求。
实际上两种方式是差不多的,以 \(c\) 的斜率拉伸后得到的最小纵坐标的点,在拉伸前其实就是斜率 \(-c\) 的直线切凸包得到的切点。
算法流程
首先想办法证明答案关于段数是凸的:一般的方式为打表(考场上的最佳方式)或者结合实际意义。
二分 \(c\),然后 dp 计算不限段数,每增加一段额外代价为 \(c\) 的最小代价,dp 的同时计算选的段数 \(x\),直到。
实际实现的时候可能遇到 \((i,f_{n,i})\) 上三点共线,如果 \(k\) 在这条线中间的话,我们无论如何都无法找到一个 \(c\) 使求出的 \(x\) 恰好等于 \(k\)。此时根据我们的算法求得的 \(x\) 是代价最小基础上的最大还是最小段数,对于二分时左右端点的赋值需要分类讨论,注意细节,如果搞错了容易死循环。
在三点共线的情况下,斜率为 \(-c\) 时求出的 \(f'_{n}\) ,使 \(f'_{n}\) 取得最小值时的段数为 \(x\)。此时答案是 \(f'_n -ck\) 而不是 \(f'_n -cx\) ,原因结合 WQS 二分的意义。
应用
用于限制段数,且答案关于段数是凸的 DP 或者一些其他问题(比如最小度限制生成树),可以使用 WQS 二分去掉段数限制。