WQS 二分
WQS 二分用来处理一些答案构成凸函数的问题。
最经典、最常见的形式,就是 "从若干个物品中恰好选给定个数的最优" 型问题。
适用要求:如果不考虑选的物品的个数限制,可以很快求出答案。
最经典例题:P2619 Tree I
从所有白边中选 \(need\) 条,然后加上若干条黑边形成生成树,最优是多少。
为什么这是一个凸函数?记选 \(x\) 条白边的最优为 \(f(x)\)。
有没有可能 \(i_1<i_2<i_3<w\),而 \((i_2,f(i_2))\) 在 \((i_1,f(i_1)),(i_3,f(i_3))\) 连线下方?
不可能。要证明这个命题,实际等价于证明任意连续三个数 \(i-1,i,i+1<w\),\((i,f(i))\) 在 \((i-1,f(i-1)),(i+1,f(i+1))\) 连线 \(l\) 上方。
采取反证法。如果 \((i,f(i))\) 在连线 \(l\) 严格下方。
从 \(i-1\) 的最优方案到 \(i\) 的最优方案,只替换了一条黑边 \(a\) 为白边;从 \(i\) 到 \(i+1\),也只替换了一条黑边 \(b\) 为白边。
而 \((i,f(i))\) 在 \(l\) 下方,说明替换 \(a\) 的收益不如替换 \(b\) 的收益,那 \(i\) 的最优方案不应该是替换 \(a\) 而是替换 \(b\)。矛盾。类似的,\(w<i_1<i_2<i_3\) 时,\((i_2,f(i_2))\) 也一定在 \(l\) 上方。
因此 \(f(x)\) 构成上凸函数。
证明是凸函数,然后怎么做?
上凸函数有一个性质:考虑一条斜率为 \(k\) 的直线与 \(f\) 的切点横坐标 \(X\),满足 \(f\) 随着 \(k\) 的减小而增大。(在图上画一下就能看出来)
如果我们能求出切点横坐标 \(X\),将其与 \(need\) 比较,就可以得到我们二分的 \(k\) 是多少。我们的目标是找到一个 \(k\) 使它与 \(f\) 的切点横坐标为 \(need\)(为什么后面会说)。
那么如何求出切点横坐标 \(X\)?已知信息只有一个 \(k\)。而且注意我们必须快速求出 \(X\),如果是平方级别的,算法根本没有优化反而还慢了。
性质:令 \(g(x)=f(x)-k\times x\),则 \(g(X)\) 为 \(g\) 的最大值。
很好理解,因为 \(g\) 相当于求一条斜率为 \(k\) 的直线过一个点的截距。而上凸函数切点处的截距一定是最大的。
结论:\(g(X)\) 为所有物品价值减去 \(k\) 后,不限制物品个数的最大价值。
为什么?发现此时的 \(f(i)\) 会变成 \(f(i)-k\times i\),而不限制物品个数就是求 \(\max(f(i)-k\times i)=\max(g(i))\),根据上面的性质 \(g(X)\) 就是最大值。
因为我们可以快速求出不限制物品个数的最大值,所以自然也可以快速求出 \(g(X)\)。在求 \(g(X)\) 的时候顺便统计一下,可以得到 \(X\)(上面的理论保证了在最优情况下的统计结果一定是 \(X\))。
怎么求 \(X\) 结束了。于是我们可以通过二分找到一个斜率 \(K\),使每个物品价值减去 \(K\) 后不限制个数的最优解恰好是选择 \(need\) 个物品。
使每个物品价值减去 \(K\),然后跑一次不限制物品个数的算法得到答案为 \(ans\),最终答案为 \(ans+K\times need\)。(这里要特别注意记得把减去的加回来)
这道题还预留有一个问题:有可能我们根本二分不到 \(need\)!
这是一个例子,当切点有很多个且 \(need\) 在中间,怎么找到 \(need\) ?
答案是我们不需要找到 \(need\),因为都被同一个斜率切的顶点,对应的截距都是相同的。只要找到的截距是被这个斜率切的,加上 \(k\times need\) 后一定会得到 \(need\) 的最优。
具体在代码实现中,我们可以让黑白边边权相同时尽量选择白边,这样对于一系列截距相同的点,我们总会找到最右边的那一个。
但是这里也就存在着一个问题:如果出现了上面那种情况,我们在还原最优方案的时候需要额外注意,要调整价值为 \(0\) 的物品的个数来让总物品个数变成 \(need\) 个。
【非 "恰好" 型选择物品】
上面的算法是选择 "恰好 \(k\) 个" 物品的最优。但如果问题是让我们选择 "最多 \(k\) 个" 呢?
分两种情况。
-
如果 \((k,f(k))\) 在 \(f\) 这个凸壳的左半部分(这里假定 \(f\) 是上凸)。直接求就行了。因为在左半部分,\(f(k)\) 随着 \(k\) 的增加而增加,"最多 \(k\) 个" 此时等于 "恰好 \(k\) 个"。
-
如果 \((k,f(k))\) 在 \(f\) 这个凸壳的右半部分。暂时没有什么好的方法求。
虽然没有方法求,但是这里介绍一种判断是否在凸壳右半部分的方案。
二分时将 \(l\) 设定为 \(0\)。这样二分斜率时就不会切到右半部分,导致二分失败。
【WQS 二分与费用流】
在实际题目里想证明一个函数是凸的其实并不是一件简单的事。
但根据费用流的增广路长度单调不降的性质,可以得出费用是关于流量的凸函数。而在费用流建模中,流量一般也表示 WQS 二分中 "恰好选 \(k\) 个" 的选择个数。
因此如果我们能对原问题建出费用流模型,也就表示这个问题是有凸性质的,进而用 WQS 二分。
这道题的模拟费用流做法已经写过,由此可以得出原问题的凸性:最大收益是关于种树个数的凸函数。
因此可以 WQS 二分 + DP 解决。具体而言,二分斜率 \(slope\),转移方程 \(dp[i]\) 表示前 \(i\) 个坑种若干颗树,相邻坑不能种树,每种一棵树都额外消耗 \(slope\) 的代价,的最大收益。
【其他题目】
最小度限制生成树
给你一个有 \(n\) 个节点,\(m\) 条边的带权无向图,你需要求得一个生成树,使边权总和最小,且满足编号为 \(s\) 的节点正好连了 \(k\) 条边。
同上题,二分斜率 \(slope\),然后把与 \(s\) 相连的边的边权都减去 \(slope\),跑 MST。
忘情
题意:一段序列的代价是 \((sum+1)^2\)。要求恰好分 \(m\) 段,使得总代价最小。
做法很简单(甚至我觉得比 Tree I 简单),wqs 二分套一个斜率优化 DP 即可。
这里二分结束后应选用 \(r\) 作为最终斜率,分析类似下面的 "LCT" 结尾的分析。
CF739E
有 \(a\) 个普通球和 \(b\) 个大师球以及 \(n\) 只宝可梦。第 \(i\) 只宝可梦被普通球捕捉的概率为 \(a_i\),被大师球捕捉的概率为 \(b_i\)。
捕捉一只宝可梦可以用一个普通球/一个大师球/普通球大师球各一个。(普通球大师球各一个的情况如果都成功了也只算作捕捉了一只宝可梦)
问最优策略下捕捉宝可梦个数的期望值。
当 wqs二分 应用在 DP 的题目上,一般叫做 DP 凸优化。对于 DP 凸优化的题目,先写出普通 DP 方程。
\(dp_{i,x,y}\) 表示前 \(i\) 个宝可梦,有 \(x\) 个普通球和 \(y\) 个大师球的期望捕捉个数。
\(dp_{i,x,y}=\max\{dp_{i-1,x,y},dp_{i-1,x-1,y}+a_i,dp_{i-1,x,y-1}+b_i,dp_{i-1,x-1,y-1}+a_i+b_i-a_ib_i\}\)
直接转移是 \(O(n^3)\) 的。但是我们能上 wqs 二分。但是这里是 "最多选取 \(k\) 个",不是恰好,怎么用呢?
显然可以观察到,球越多,期望捕捉的个数就越大。所以 \(dp_{i,x,y}\) 在 \(i,x\) 固定时,\(dp_{i,x,y}\) 随着 \(y\) 的增大而增大。
因此虽然是 "最多选取 \(k\) 个",但是相当于是 "恰好选取 \(k\) 个"。可以用 wqs 二分。现在的状态描述变成 \(dp_{i,x}\) 表示前 \(i\) 个宝可梦最多用 \(x\) 个普通球和无限个大师球,每用一个大师球就要减去 \(slope\) 的最大期望。
于是复杂度优化到 \(O(n^2\log n)\)。这里已经可以通过了,但我们还能继续加速。
发现算法的瓶颈在于求 \(dp_{i,x}\) 需要 \(O(n^2)\) 的复杂度。但是 \(dp_{i,x}\) 在 \(i\) 一定时,\(dp_{i,x}\) 随着 \(x\) 的增加而增加。因此我们可以在 wqs 二分里面再套一个 wqs 二分求 \(dp_{i,x}\)。复杂度 \(O(n\log^2 n)\)。