凸优化乱写

推荐阅读:凸优化常见技巧 做题笔记-KingPowers 以及文中的推荐文章。

wqs 二分

我们要解决的问题一般都是类似于区间分拆问题,其 2D-1D 递推式一般都类似:

fk,i=opt{fk1,j+w(j+1,i)}f_{k,i}=\operatorname{opt}\{f_{k-1,j}+w(j+1,i)\}

其中 w(i,j)w(i,j) 满足四边形不等式。设 g(k):=f(n,k)g(k):=f(n,k),那么有 g(k)g(k) 是凸函数。

proof.\text{proof.}

尝试证明 g(k1)+g(k+1)2g(k)g(k-1)+g(k+1)\geq 2g(k)。考虑一个长度为 k1k-1 的分拆 [a1,d1],,[ak1,dk1][a_1,d_1],\dots,[a_{k-1},d_{k-1}] 与一个长度为 k+1k+1 的分拆 [b1,c1],,[bk+1,ck+1][b_1,c_1],\dots,[b_{k+1},c_{k+1}]。然后想着怎么去构造出两个长度为 kk 的分拆,也就是把上面两个融合在一起,于是想到找一个最小的 jj 满足 cj+1djc_{j+1}\leq d_j,由 dk1=nd_{k-1}=n 可知其必然存在,随后根据其最小性,我们知道aj<bj+1cj+1dja_j<b_{j+1}\leq c_{j+1}\leq d_j,于是从 jj 把序列拆开,把两端末尾交换得到

[a1,d1],,[aj1,dj1],[aj,cj+1],[bj+2,cj+2],,[bk+1,ck+1]    [b1,c1],,[bj+1,dj],[aj+1,dj+1],,[ak1,dk1][a_1,d_1],\dots, [a_{j-1},d_{j-1}],[a_j,c_{j+1}],[b_{j+2},c_{j+2}],\dots,[b_{k+1},c_{k+1}] \iff [b_1,c_1],\dots ,[b_{j+1},d_j],[a_{j+1},d_{j+1}],\dots,[a_{k-1},d_{k-1}]

于是 2g(k)2g(k) 小于等于上面的区间的权值加一块,由四边形不等式可知 2g(k)g(k1)+g(k+1)2g(k)\leq g(k-1)+g(k+1)

Q.E.D.\text{Q.E.D.}

以上文字是我之前在 cnblogs 上写的,自以为掌握了一个数学模型能够不用脑子利用 wqs 二分切题的方法,可这样的数学模型真的足够普适吗?显然不!也许王钦石在发明这个方法的时候确实是从 dp 问题中归纳出来的数学模型,但经过后人的推广之后,wqs 二分的应用范围已经不仅仅局限于 dp。更普适的模型如下:

对于定义在自然数域上、值域为正整数的凸包 f(x)f(x),要求在某个特定点上的函数值,而我么难以直接求 f(x)f(x),但是容易求出 f(0)f(0)。我们利用函数的凸性,也就是 δf(x)\delta f(x) 单调的性质二分斜率,考虑这个斜率 kk的直线切凸包的时候,所对应的切点满足的就是 f(x)xkf(x)-xk 最小,实际应用中相当于是对每个点值强制加上一个 k-k 的权值然后计算 f(0)f'(0),当然这对于讨论数学模型中没有意义。

那我之前写的真的就一无是处吗?显然不!把那一大段贴上是因为这个二级结论非常常用,能方便我们做题。


[国家集训队] Tree I

给定一个无向带权连通图,每条边是黑色或白色,求一棵最小权的恰有 kk 条白边的生成树,V5×104,E105|V|\leq 5\times 10^4,|E|\leq 10^5

f(x)f(x) 表示恰有 xx 条白边的生成树的最小权,打表发现 f(x)f(x) 是个凸包,二分斜率 pp,给每白边的权值减 pp 跑 MST 就行。


Luogu P4983 忘情

算得上板子中的板子了,直接搬运我之前写的。

fif_i 表示在 ii 处分段,前 ii 段的最小值,记 si=j=1iais_i=\sum_{j=1}^ia_i,有转移

fi=minj=1i1{fj+(sisj)2+c}=min{fj+si22sisj+sj2+c}=min{2sj×si+sj2+fj}+si2+cf_{i}=\min_{j=1}^{i-1}\{f_j+(s_i-s_{j})^2+c\} \\ = \min\{f_{j}+s_i^2-2s_is_j+s_j^2+c\} \\ = \min\{-2s_j\times s_i +s_j^2+f_j\}+s_i^2+c

如果对于某个 cc,求出来的答案正好分成了 mm 段,那么求出来的答案减去 ckck 就是我们要求的。显然地,cc 越大,段数越小,所以我们二分 cc,获得一个段数恰好为 mm 的答案。如果对于 c=xc=x,段数小于 mm,对于 c=x+1c=x+1,段数大于 mm,那么取 c=x+1c=x+1 时也存在分段数为 kk 的做法,只需把答案减去 (x+1)m(x+1)m。这样我们就得到了 O(nlogC)O(n\log C) 的做法,很优!


Luogu P5633 最小度限制生成树

给你一个有 nn 个节点,mm 条边的带权无向图,你需要求得一个生成树,使边权总和最小,且满足编号为 ss 的节点正好连了 kk 条边,1sn5×1041\leq s\leq n \leq 5\times 10^41m5×1051\leq m \leq 5\times 10^51k1001\leq k \le 1000w3×1040\leq w\leq 3\times 10^4

f(x)f(x) 表示 ss 连了 xx 条边的最小生成树权值和,尝试证明 f(x)f(x) 是凸的。

proof.\text{proof.}

显然对 T=1,2|T|=1,2 的时候成立。

假设对于 Tn|T|\leq n 均成立。设当前的树 T=n|T|=n,最小边是 ee,删去 ee 后两棵树是 T1,T2T_1,T_2,发现 fT(k)=min(fT1(k1)+fT2(k2))f_T(k)=\min(f_{T_1}(k_1)+f_{T_2}(k_2))(min,+)(\min,+) 是两个函数的闵可夫斯基和,由归纳假设可知对所有 TT 函数都是凸的,\square

于是二分斜率,给 ss 连的边权减去 midmid 跑 MST 就行。

Minkowski 和

定义是对于两个点集 A,BA,B,它们的闵可夫斯基和为点集 C={a+baA,bB}C=\{a+b|a\in A, b\in B\}

这对于普通的点集来说并没有什么值得研究的,但是对于两个凸包来说就有的搞头了。盗一下经典图。

image

对上面两个凸包做 Minkowski 和,得到新的点集:

image

发现把边看成向量的话新凸包的边就是把原来两个凸包的边按照斜率排序之后顺次拼接形成的。这非常有用,假设有 f,gf,g 两个凸壳我们可以 O(f+g)O(|f|+|g|) 合并为新的凸壳,更具体的,对于两个下凸壳 f,gf,g(min,+)(\min,+) 卷积,也就是 hk=minj+k(fj+gk)h_{k}=\min_{j+k}(f_j+g_k),因为 f,gf,g 都是下凸壳,所以 hh 就相当于 f,gf,g 上点相加后对每个 xx 取最小的点,发现这就是闵和,也就是说对下凸的数组做 (min,+)(\min,+) 卷积是线性的,对于上凸的函数做 (max,+)(\max,+) 卷积同理。代码辅助理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline vector<int> merge(vector<int> a, vector<int> b)
{
vector<int> c;
c.pb(a[0] + b[0]);
int l = 1, r = 1;
while (l < a.size() && r < a.size())
{
if (a[l] - a[l - 1] < b[r] - b[r - 1]) c.pb(a[l] - a[l - 1]), ++l;
else c.pb(b[r] - b[r - 1]), ++r;
}
while (l < a.size()) c.pb(a[l] - a[l - 1]), ++l;
while (r < b.size()) c.pb(b[r] - b[r - 1]), ++r;
fo(i, 0, c.size() - 1) c[i] += c[i - 1];
return c;
}

2020 ICPC Shenyang R L.Forged in the Barrens

给定数列 {an}\{a_n\},你需要将它分为 kk 部分,每部分的价值定义为极差,对所有 k[1n]Zk\in [1\dots n]\cap \mathbb{Z},求最大价值和。n105,ai109n\leq 10^5,a_i\leq 10^9

套路化的设 fi,jf_{i,j} 表示前 ii 个数分了 jj 段的最大价值和,直接枚举前面的位置是 O(n3)O(n^3) 并且没有特殊性质,难以优化到一个可过的做法,考虑一个区间的极差等价于任选两数相减的最大值,于是设 fi,j,0/1/2f_{i,j,0/1/2} 表示前 ii 个数选了 jj 段,最后一段对答案有负贡献/零贡献/正贡献时的最大价值,这样就能做到 O(1)O(1) 转移从而做到 O(n2)O(n^2),似乎到这里就卡住了,不过打出表来发现 fnf_n 关于 kk 是上凸的。

然后有一个技巧就是把序列上的 dp 当做区间 dp 去做,设 fl,r,0/1/2,0/1/2,kf_{l,r,0/1/2,0/1/2,k} 表示区间 [lr][l\dots r] 分了 kk 段,其中左端点、右端点对答案的贡献分别是负贡献/零贡献/正贡献,转移时考虑任取一个断点 midmid,有

fl,r,ol,or,k=max{max(fl,mid,ol,or,k,fmid+1,r,ol,or,k)maxx+y=k(fl,mid,ol,1,x+fmid+1,r,1,or,x)maxx+y=k1(fl,mid,ol,0,x+fmid+1,r,2,y)maxx+y=k1(fl,mid,ol,2,x+fmid+1,r,0,y)f_{l,r,ol,or,k}=\max\begin{cases} \max(f_{l,mid,ol,or,k},f_{mid+1,r,ol,or,k}) \\ \max\limits_{x+y=k}(f_{l,mid,ol,1,x}+f_{mid+1,r,1,or,x})\\ \max\limits_{x+y=k-1}(f_{l,mid,ol,0,x}+f_{mid+1,r,2,y})\\ \max\limits_{x+y=k-1}(f_{l,mid,ol,2,x}+f_{mid+1,r,0,y}) \end{cases}

midmid 取在中点的时候就得到了类似线段树的分治结构,时间复杂度 O(nlogn)O(n\log n)


2022 ICPC Nanjing R H.Factories Once More

nn 个点的树,边带权,树上选 kk 个点使两两距离和最大,1kn2×1051\leq k\leq n\leq 2\times 10^5,边权在 [1,109][1,10^9] 中间。

暴力树背包是设 fu,if_{u,i} 表示点 uu 的子树里面选了 ii 个点的答案,考虑合并子树,fu,x=maxi+j=x{fu,i+fv,j+w(u,v)j(kj)}f_{u,x}=\max_{i+j=x}\{f_{u,i}+f_{v,j}+w(u,v)\cdot j\cdot (k-j)\},发现 j(kj)j(k-j) 是开口向下的二次函数,每次合并做的是 (max,+)(\max,+) 卷积,可证背包是上凸的,于是每次我们要做的就是两个差分数组相加,考虑 +w(u,v)j(kj)+w(u,v)j(k-j) 对差分的影响是怎么样的,写式子发现是 w(k2j+1)w(k-2j+1) 是个一次函数,考虑维护一个本身有序的数据结构然后 dsu on tree 地合并,也就是说维护平衡树,对子树打一个一次函数的 tag 就行了。发现 fu,0=0f_{u,0}=0 可以不维护,时间复杂度 O(nlog2n)O(n\log^2n)

slope trick

什么是 slope trick?如图:

image

用于优化呈分段一次函数状的 dp,当然也要求是凸的,也就是说 g(x)=Δf(x)g(x)=\Delta f(x) 是单调的,尝试维护 g(x)g(x) 变化的拐点,维护一个集合,每扔进一个点意味着这个点往后斜率变化了 1,扔进几次斜率就变化了多少,这要求斜率变化不是很大。一般考虑每一步 dp 转移对斜率的影响。后面称“斜率为 kk 的拐点”为后面直线斜率为 kk 的点。

dp 的设计并非该算法的重点(虽然对于题目至关重要),而凸性证明在闵和的基础上已经有了充足的技术支撑,因此学 slope trick 重点是把转移对差分数组的影响想明白。


CF713C Sonya and Problem Without a Legend 加两个 0

给定 {an}\{a_n\},每次可以选一个数加一或减一使序列单增,求最小次数,n3×105,1ai109n\leq 3\times 10^5,1\leq a_i\leq 10^9

aiaiia_i\leftarrow a_i-i,问题转化成单调不减,这样改一个数只可能改成之前出现过的一个数。设 dpi,jdp_{i,j} 表示 aija_i\leftarrow j 的最小次数,有转移 dpi,j=mink=1j{dpi1,k}+aijdp_{i,j}=\min_{k=1}^j \{dp_{i-1,k}\} + |a_i-j|,这样直接做就是 O(n2)O(n^2) 的。设 fi(x)=dpi,xf_{i}(x)=dp_{i,x},归纳可证 f(x)f(x) 是凸的,考虑维护拐点,dp 过程相当于前缀 min 与加 aix|a_i-x|,对于前缀 min 操作抹掉斜率大于 0 的部分就行了,对于加 aix|a_i-x|,找到斜率为 0 的拐点 pp,如果 aipa_i\geq p 那么直接加入 aia_i,否则 aia_i 位置斜率变化量为 2,后面的会被抹平,直接扔掉就行了。


CF372C Watching Fireworks is Fun 加七个 0

nn 各区域,mm 个烟花要放,给定地点 aia_i 与时间 tit_i,如果你在 xx 会获得 biaixb_i-|a_i-x| 的开心值,每个单位时间最多移动 dd,求通过移动最大开心值,n109,m2×105,ai,bi,ti109n\leq 10^9,m\leq 2\times 10^5,a_i,b_i,t_i\leq 10^9tit_i 单增。

相当于 aix\sum |a_i-x| 最小值,暴力 dp 设 dpi,xdp_{i,x} 表示第 ii 个烟花在 xx 的最大开心值,转移是滑动窗口,dpi,x=aix+minxdΔtyx+dΔtdpi1,ydp_{i,x}=|a_i-x|+\min_{x-d\Delta t\leq y\leq x+d\Delta t}dp_{i-1,y}fi(x)=dpi,xf_i(x)=dp_{i,x} 于是 f(x)f(x) 显然是凸的考虑维护拐点,minxdΔtyx+dΔtdpi1,y\min_{x-d\Delta t\leq y\leq x+d\Delta t}dp_{i-1,y} 相当于对于小于等于 0 的拐点左移 dd,大于 0 的拐点右移 dd+aix+|a_i-x| 考虑最小值区间 [l,r][l,r] 如果 ai[l,r]a_i\in [l,r] 相当于 aia_i 位置上加两个拐点,ai<la_i<l 相当于 aia_i 上的斜率 -2 并成为新的 0 斜率点,ai>ra_i>r 与上面的对称,然后开两个堆维护小于等于 0 和大于 0 的拐点,平移直接打标记,最小值实时维护即可。


[OOI 2025] The arithmetic exercise

给定 {an}\{a_n\},初始时全为 0,同时 mm 个数 x1,x2,,xmx_1,x_2,\dots,x_m,对于 1im1\leq i\leq m 选择某个下标 jij_i,使 ajixajia_{j_i}\leftarrow x-a_{j_i},求操作完后 ai\sum a_i 的最大可能。n,m300000,109xi109n,m\leq 300000,-10^9\leq x_i\leq 10^9

正着做会导致前面定了符号的 xix_i 被取反这很不好维护,反着做就没有这样的问题所以把序列取反后缀改前缀,假设符号序列为 {kn}\{k_n\},那么这个序列合法当且仅当 i[1,n],0j=1ikin\forall i\in [1,n],0\leq\sum_{j=1}^ik_i\leq n,于是枚举这个值,设 fi,jf_{i,j} 表示考虑到 xix_ikik_i 的前缀和为 jj 的答案,有转移 dpi,j=max{dpi1,j1+xi,dpi1,j+1xi}dp_{i,j}=\max\{dp_{i-1,j-1}+x_i,dp_{i-1,j+1}-x_i\} 发现这相当于关于 jj 的函数 dpi,jdp_{i,j}xi,xi{-x_i,x_i}(max,+)(\max,+) 卷积(钦定 xi-x_i 的下标为 -1,xix_i 下标是 1),考虑归纳证明 fi(j)=dpi,jf_i(j)=dp_{i,j} 的凸性。对于 f0f_0 凸性显然,对于 i1ii-1\rightarrow i 的转移由 Minkowski 和经典结论可知如果 fi1f_{i-1} 是凸的那么 fif_i 也是凸的,由归纳原理凸性得证。用闵和的方式维护差分的形式是不可行的,考虑 slope-trick 地维护拐点,考虑一次转移 i1ii-1\rightarrow i 的影响,发现这种两边向中间的平移不好维护,考虑先把答案减去 xi\sum x_i,这样先把 fi1f_{i-1} 平移,然后 dpi,j=max{dpi,j,dpi1,j2+2xi}dp_{i,j}=\max\{dp_{i,j},dp_{i-1,j-2}+2x_i\},又发现 dpi,jdp_{i,j} 有值当且仅当 i,ji,j 奇偶性相同,于是 j2jj-2\rightarrow j 可以改成 j1jj-1\rightarrow j,改一下范围即可,弄明白这些就可以 multiset 维护拐点做了。


凸优化乱写
https://lotus-grass.github.io/2025/07/28/凸优化乱写/
作者
lotus_grass
发布于
2025年7月28日
许可协议