凸优化 DP:k-子段和 系列问题

给定长度 \(n\) 的序列 \(a_1\sim a_n\)

问题 1:给定固定的 \(k\),选 \(k\) 个不相交的子段,和最大是多少。

问题 2:支持两种操作,全局加和查询区间最大子段和,允许离线。

问题 3:对 \(k=1\sim n\),求选 \(k\) 个不相交的子段,和最大分别是多少。

问题 4:询问 \(q\) 次,每次询问从一个区间选 \(k\) 个不相交的子段,和最大是多少。

问题 1

法一:使用 WQS 二分。\(dp[i]\) 表示前 \(i\) 个数任意分段,每多一段就多 \(-x\) 的最大收益。\(dp[i]=\max(dp[i-1],\max_j(dp[j]+s[i]-s[j-1]))\),拆一下式子即可。复杂度 \(O(n\log V)\)

法二:模拟费用流。线段树支持查询最大子段和即可。复杂度 \(O(k\log n)\)

问题 2

本题即 P5073 世上最幸福的女孩

离线操作。把全局加 \(k\) 的操作删除,转而给第二种操作升维:\((l,r,k)\) 表示询问 \(a_l+k,a_{l+1}+k,\dots,a_{r}+k\) 的最大子段和。

考虑用线段树处理这个问题。最大子段和总共需要四个信息:最大前缀和、最大后缀和、区间总和、最大子段和。

  1. 区间总和。这是好求的,对于参数 \((l,r,k)\),其区间总和是 \(\sum_{i=l}^{r}a_i+k=s_r-s_{l-1}+k\times (r-l+1)\)

  2. 最大后缀和。这和最大前缀和类似。

  3. 最大前缀和。

    先考虑对一个结点怎么求。

    在结点 \([l,r]\) 上,用 \((i,f_i)\) 表示 \(a_{l\sim r}\) 有一个长度 \(i\) 的前缀,其和为 \(f_i\),则对于 \((l,r,k)\),最大前缀和就是 \(\max\{i\cdot k+f_i\}\),容易发现这只会取到 \((i,f_i)\) 的上凸壳。如果能预先求出上凸壳,就可以二分求最大前缀了。

    怎么求上凸壳?先求左右儿子的上凸壳,给右边的凸壳向上平移左边的总和(这显然还是个凸壳),然后利用闵可夫斯基和线性合并。一共 \(O(\log n)\) 层,每层都是线性合并,预处理凸壳复杂度 \(O(n\log n)\)

    现在知道怎么对于一个结点求它的最大前缀和了,多个结点时按顺序合并即可。

  4. 最大子段和。

    有了前后缀,最大子段和也很简单了。先递归左右儿子,先取左右儿子的最大子段和,然后用左儿子的最大后缀和右儿子的最大前缀拼起来。

把询问区间拆成 \(O(\log n)\) 个线段树结点即可。

但如果这样子做是 \(O(n\log^2 n)\) 的,或许其他题可以过,但是 Ynoi 的题还是给予足够的尊重吧。我们要优化到 \(O(n\log n)\)。我们发现,如果把这些询问再离线一次,把所有询问按 \(k\) 排序,每个凸壳的最优决策点是单调向右的,用指针即可解决。复杂度 \(O(n\log n)\)

但是这样做还是通过不了本题。因为空间复杂度是 \(O(n\log n)\) 的。瓶颈在于需要把所有凸包在对应结点都存下来。如果能每次只存一层的凸包就好了。一个想法是把所有询问记录到每个对它生效的结点上,在结点处处理所有涉及它的询问,然后不保存它的空间。然而这样凸包的复杂度少了,但是把询问拆出来的复杂度变成 \(O(m\log n)\) 了。
但这给了我们启发,我们直接带着所有询问向下递归,判断该询问在该区间是否生效,是否下放到左右儿子即可(下放后要释放空间或是直接使用单链表移过去下放!),这样每个时刻每个询问至多只会在两个节点上。空间复杂度 \(O(n+m)\)

问题 3

法 0.5:WQS 二分。复杂度 \(O(n^2\log n)\)

法一:模拟费用流。复杂度 \(O(n\log n)\),每多流一个就是更大的答案。

法二:凸优化 DP。

闵和、凸优化常和分治/区间 DP 搭配,因为凸壳合并可以线性,分治 \(\log n\) 层,总复杂度就是 \(O(n\log n)\) 的。本题是一个例子。

\(f[l,r,0/1,0/1](x)\) 表示在 \([l,r]\) 区间选 \(x\) 个互不相交的段,\(l\) 选不选、\(r\) 选不选 的最大和。令 \(dp[l,r](x)=\max f[l,r,?,?](x)\)。根据费用流模型可以知道 \(f,dp\) 都是凸的。

显然 \(f[l,r,?,?]\) 可以从 \(f[l,m,?,?]\)\(f[m,r,?,?]\) 做 <max,+> 卷积得到,根据闵和的凸性质,可以线性合并。对应的,\(dp[l,r]\) 也可以这么合并。一路合并上来可以得到 \(dp[1,n](1\sim n)\) 的凸壳。

问题 4

先考虑一个查询 \((l,r,k)\),退化到了问题 1,使用 wqs 二分即可解决。

然后沿用问题 2 的思路,在线段树上做这个问题。

考虑在一个线段树结点上怎么快速求答案。我们发现可以再利用问题 3 的想法:在每个线段树结点上记录这个结点对应的 \(dp[l,r]()\) 的凸包,预处理复杂度 \(O(n\log n)\)。然后在这个凸包上二分即可。

然后对于一次询问 \((L,R,k)\),我们想要把它拆成若干个线段树结点,各自求答案合并。但是问题来了:我们还限制了只能取 \(k\) 段,各自求肯定无法满足这个条件。怎么办?如果这些结点的凸包再合并,复杂度立刻变成平方。

既然段数是个棘手的限制,我们就想办法去掉它。在问题 1 里也有段数限制,是用 wqs 二分解决的。所以在这里我们也 wqs 二分,这样就可以每个线段树结点内各自取若干段了。复杂度 \(O(n\log n+q\log V\log^2 n)\):wqs 二分一个 \(\log\),线段树一个 \(\log\),凸包二分一个 \(\log\)。强制在线的复杂度目前到此为止了。

如果允许离线,还能更快!使用整体二分,即 wqs 二分时把当前位置的询问分成两半各自传给下面,可以少一个 \(\log\)

posted @ 2024-11-28 22:03  FLY_lai  阅读(184)  评论(0)    收藏  举报