适用范围
wqs 可以解决的问题具体来说是这样:给你一些物品,我们可以从里面选的物品是有限的,每个物品有其价值,要求最大化这个价值,价值有可能为负。
抽象来说,就是给你一些物品,有一些限制,如果没有限制的做法很简单,要求求一个最优值。
问题解决
很显然,我们可以有一个 dp, $f_{i,j}$ 表示从前 $i$ 个物品里选 $j$ 个的最优值是多少,不过这样的 dp 因为受其状态数的限制,复杂度难以下降。我们换一种方式去考虑这个问题。
令 $g(i)$ 表示选 $i$ 个物品的最优值,很显然,如果 $i$ 小于等于正价值物品数量的话,那么随着 $i$ 递增,最优值也递增,如果 $i$ 超过了正价值物品数量,那么随着 $i$ 递增,最优值也递减。所以,如果把所有的点 $(i,g(i))$ 画在坐标系中,这就是一个上凸包。
其实如果问题改一改,变成下凸包,wqs 二分也可以做,下面的例题就是一个下凸包,这里先以上凸包作为例子。
既然是上凸包,那么满足斜率递减,所以我们去二分这个斜率,很显然,不管二分的斜率是什么,都会切凸包于一点,假设这个点的横坐标是 $x$ ,那么如果我们能求出 $x$ 的话,我们就可以知道我们接下来该往哪里二分,假设我么要求选 $m$ 个物品,如果 $x<m$ 我们就往小里二分,否则往大里二分。
如果我们能够求出这个点的纵坐标,我们就可以知道这个点对应的最优值是什么,进而能够得出答案。
那么现在的问题转化成了如果求这个点。假设当前的斜率为 $k$ ,像这样:
注意:按道理说这里的每一个点都应该是整点,但如果都是整点太不好看了,于是就这样画了。
目前 $k=0.71$ ,我们想要求出 $x=3$ ,$g(x)=5$ 这个结果,怎么求呢?
我们令 $f(x)=g(x)-k\times x$ ,因为 $g(x)$ 的意义是选 $x$ 个物品的最优解,所以 $f(x)$ 为把这每个物品减去 $k$ 后的最优解。
事实上:$f(x)$ 为在没有拿多少这个限制的时候,所有物品的价值减去 $k$ 后的最优解。
这并不是很好理解,我们来论证一下这个事情。
首先一个基本的事实是:根据 $f(x)$ 的定义式,不难发现 $f(x)$ 是这条直线在 $y$ 轴上的截距,并且如果用这条直线去切其他的点,得到的截距都比这个小。
我们首先把所有的物品都减去 $k$ ,然后令 $f(x)$ 为无限制下最优,不难发现上面的事情是正确的,因为 $f(x)$ 在 $g(x)$ 时取得最优。两边都是最优值。
我再换一个角度解释一下:首先显然的一点是 $f(x)$ 是在 $g(x)$ 的基础上把每个物品的价值减去了 $k$ 。我们考虑一下把所有物品减去 $k$ 后,拿 $q$ 个的最优值,不妨设为 $h(q)$ ,不难发现,每一个 $h(q)$ 都对应着斜率为 $k$ 这个点切 $(q,g(q))$ 这个点后在 $y$ 轴上的截距。因为 $f(x)$ 是最大的截距,所以有 $h(x)=f(x)$ ,又因为 $h(x)$ 在所有 $h$ 中最大,所以 $f(x)$ 就是减去 $k$ 后全局最优。
那么我们就可以这样求 $x,g(x)$ :只需要关注减去 $k$ 后无限制下最优的拿取方案拿了多少,这就是 $x$ ,用这个最优值加上 $k\times x$ ,就是 $g(x)$ 。在一般的题目中,这件事情在 $O(n)$ 的复杂度内可以处理。
所以总复杂度应该是 $O(n\log n)$ 。
细节处理
我们把刚刚的图再放下来。
你会发现这里有很多切不到的点,比如说点 $H,I,J,K,G$ ,原因就是左右两边斜率是相同的,这个时候怎么办?我们关注一下下面的例题。
例题
用 $g(i)$ 表示白边数量为 $k$ 时最小权值。
感性理解一下,如果我们直接求最小生成树,假如这颗树里一共有 $m$ 条白边,那么 $g(m)$ 是最小的,当这个白边无论是减少还是增加,都会带来限制。这个时候 $g(i)$ 就会变大,所以由 $(i,g(i))$ 组成的所有点是一个下凸壳。
下凸壳斜率递增,我们去二分这个斜率,设当前二分的值为 $mid$ 。
我们考虑如何计算 $f(x)$ ,不难发现,$f(x)=g(x)-mid\times x$ ,其中 $x$ 为白边的数量。因为 $x$ 的定义,我们就把所有白边的权值减去 $mid$ 后做最小生成树,$x$ 就是这个最小生成树中白边的数量。
不过如果你真的这样做,结果可能回事全 WA ,为什么,原因就是出现了上述情况。
我来说一下我是如何避免的:
这是一个下凸壳,且因为都是整点,且点与点之间相隔为 $1$ ,所以斜率都是整数。如图:
读者就当所有点都是整数
假如说我们想求 $g(7)$ 是多少,但是因为左右两边斜率相等,怎么办?
我们这样做:求最小生成树我们排序时,默认相同权值下,白边在前,也就是说我们让他选的白边尽量多,所以当斜率恰好是 $DE$ 这条直线的斜率时,会返回 $9$ 。
同时我们二分是这样做,我们让他二分时停在白边数量大于 $need$ 的所有斜率中最小的那个。
放下代码理解一下:
check
:
1 | inline int check(int mid){ |
二分:
1 | while(l<r){ |
如果我们最终要求的是 $g(7)$ 的话,这条斜率最后会停在和线段 $DE$ 的斜率相同的位置。因为我们总是不要小的,大的可以保留。那么知道了这条直线的斜率,其实剩下的就好办了,我们最后在引用一下 check(l)
,这样就相当于把切 $7$ 的斜率放进去在做一遍,时候加上 need*cnt
就可以了。
代码:
1 |
|
wqs 二分与费用流
有一些题目难以证明凸性,但是如果其可以建立费用流模型,因为费用流函数,即 $F(x)$ 表示流为 $x$ 时的最小费用,则一定是凸的,所以可以用费用流模型来看其是否为一个凸函数。