四边形不等式&决策单调性
立柱已选162534 · · 算法·理论
原 PPT
理论上是我的 sky 课件,但是没讲,所以成为了一篇专栏。
updated on 2025.4.26
一周年纪念,回来看一眼并修 markdown。
typst 更新后因未知原因坠机,只能在这更新了(
updated on 2025.8.8~2025.8.13
最新最热分治出炉,加入相关解释。
渲染更新,修 markdown 并加入折叠框。
投了全站推荐。
updated on 2026.1.8
大改,现在人类能够看懂了,同时改掉了一些错误。
updated on 2026.2.28
代码排版炸了,修改。
一、四边形不等式
\
定义 1:已知
:::info[为什么它叫四边形不等式]
在凸四边形
| 不知道这东西有没有什么实际的用处。 |
|---|
定义 2:若对任意
这两个定义是等价的。
-
若将定义 1 的
x_1,x_2,y_1,y_2 分别设为x,x + 1,y,y + 1 ,就是定义 2 的形式。 -
考虑对定义 2 使用数学归纳法,若对
x_{1},x_{2},y_{1},y_{2} 与x_{2},x_{3},y_{1},y_{2} 已经满足四边形不等式,x_{1},x_{3},y_{1},y_{2} 也满足四边形不等式,那么就可以由x,x + 1,y,y + 1 推至x,x',y,y + 1 ,在y 方向同理。
:::info[证明]
形象一点地来讲:
考虑这么一个方格,其中第
这两张图分别代表
将红绿两色部分分别加起来:
由于红绿两色分别对应不等式的两侧,因此可以消去相同的部分,再加起来得:
| 也就是 |
|---|
由此,可得验证四边形不等式的代码如下:
for(int i=1;i<n;++i)
for(int j=i;j<n;++j)
assert(w(i,j)+w(i+1,j+1)<=w(i,j+1)+w(i+1,j));
如果程序没有出现报错,那么四边形不等式成立。
在考场上一般用这种方法判定四边形不等式,而在实际证明中一般会使用倒推法,将
容易发现,在四边形不等式中任何只关于
严格来说,设
证明:
二、决策单调性及对应优化
\
首先,看已知满足四边形不等式的
本文中定义
对于每个状态点
决策单调性:
注意:根据决策单调性直接暴力计算不会优化时间复杂度,如果
由四边形不等式可以推知决策单调性
考虑用反证法,设
对
而由于上面的定义,
显然矛盾,故决策单调性成立。
对于以上内容,将
这种情况下判定代码如下:
for(int i=1;i<n;++i)
for(int j=i;j<n;++j)
assert(w(i,j)+w(i+1,j+1)>=w(i,j+1)+w(i+1,j));
为方便考虑,以下内容只考虑
已知决策单调性后的一些优化
对于之前的问题,若满足决策单调性,有几个方法优化。
- 单调队列与斜率优化
和主题无关,不讲。
- 分治
这里分治的是状态点,同时会下传当前可能的决策点。
对于每个状态点区间
由于决策单调性,计算
显然每一层的计算量均为
参考代码,注意可能
:::info[code]
void solve(int l,int r,int L,int R){
//l与r代表要求区间[l,r]的f,L与R代表p的范围
int mid=(l+r)>>1;
for(int i=L;i<=min(mid-1,R);++i)
if(w(i,mid)<f[mid])f[mid]=w(i,mid),p[mid]=i;
if(mid>l)solve(l,mid-1,L,p[mid]);
if(mid<r)solve(mid+1,r,p[mid],R);
}
:::
2. 单调队列 + 二分
为了能处理 DP 转移(半在线),考虑从前往后枚举状态点,加入当前状态点为决策点并维护所有的决策点。
考虑每一个决策点,由于
于是在加入决策点时二分这一段状态点的范围(不妨设决策点
具体来说,当考虑状态点
-
如果队列为空,直接入队,此时
l_{i} = i + 1,r_{i} = n 。 -
否则,设队尾为
j' 。-
如果
i 比j' 劣,将i 拼在j' 的后面,即l_{i} = r_{j} + 1,r_{i} = n 。注意这种情况需要入队当且仅当r_{j'} < n ,出现这种情况是因为r_{j'} = n 的队尾被出队了(见下文)。 -
否则,分两类讨论
i 比j' 优,需要入队的情况:-
若对
\left[ l_{j'},r_{j'} \right] ,i 均比j' 更优,将j' 出队。由于i \geq j' 与决策单调性,只需要判断w_{j',l_{j'}} > w_{i,l_{j'}} 即可,这种情况可能会多次出现。 -
若对
\left[ l_{j'},r_{j'} \right] 中的一部分,i 比j' 更优,将i 入队并用二分判断i 开始比j' 更优的位置,设这个位置为k 则r_{q'} = k - 1,l_{i} = k,r_{i} = n 。
-
-
同样需要注意计算
:::info[code]
deque<int> q;
l[1]=1,r[1]=n;
q.push_back(1);
for(int i=2;i<=n;++i){
while(!q.empty()&&i>r[q.front()])q.pop_front();//处理过时元素
ans[i]=w(q.front(),i),fr[i]=q.front();
while(!q.empty()&&w(q.back(),l[q.back()])>=w(i,l[q.back()]))
q.pop_back();//处理 i 完全优于 j' 的情况
if(q.empty()){//处理队列为空的情况
q.push_back(i);
l[i]=i+1,r[i]=n;
}
else if(w(q.back(),r[q.back()])<w(i,r[q.back()])){
if(r[q.back()]<n){//将i接在j'后面
q.push_back(i);
l[i]=r[q.back()]+1,r[i]=n;
}
}
else{
int L=l[q.back()],R=r[q.back()],mid,p;
while(R>=L){//二分
mid=(R+L)/2;
if(w(q.back(),mid)>=w(i,mid))p=mid,R=mid-1;//记录i开始优于j'的时间
else L=mid+1;
}
r[q.back()]=p-1,l[i]=p,r[i]=n;
q.push_back(i);
}
}
:::
也可以类似斜率优化,每次入队和出队时直接二分计算临界点,时间复杂度不变,但是这样二分次数比较多(我也不想写),不提供代码。
3. 最新最热分治(丐版 LARSCH 算法)
这个东西递归的也是状态点。
分治难以处理 DP 转移(半在线)的原因在于
记只考虑
惊人地发现,这样确实算出了所有点正确的
模仿原文,下文选择递归
具体步骤如下:
-
预处理
p_1 、p_{n,1} -
进入递归,在
[p_l,p_{r,l}] 求p_{\text{mid},l} -
递归
[l,\text{mid}] ,得到p_{l+1}\dots p_{\text{mid}} -
扫一遍
[l+1,\text{mid}] 求出p_{r,\text{mid}} -
递归
[\text{mid},r] ,得到p_{\text{mid}+1}\dots p_r -
当
r=l+1 时计算原地转移,结束递归
生动形象(?地,下图按递归顺序展示了递归的过程,奇数行表示当前考虑的区间,浅色部分表示
显然,时间复杂度不变。
代码实现如下:
:::info[code]
void solve(int l,int r){
if(r==l+1){
if(w(r,r)<dp[r])dp[r]=w(r,r),p[r]=r;
return;
}
int mid=(l+r)>>1;
p[mid]=p[l],dp[mid]=w(p[mid],mid);
for(int i=p[l]+1;i<=p[r];++i)
if(w(i,mid)<dp[mid])dp[mid]=w(i,mid),p[mid]=i;
solve(l,mid);
for(int i=l+1;i<=mid;++i)
if(w(i,r)<dp[r])dp[r]=w(i,r),p[r]=i;
solve(mid,r);
}
:::
4. 其它做法
SMAKW 等,可以做到支持半在线的
例题
三、四边形不等式(决策单调性)实战
例题 1:lightning conductor(四边形不等式证明 / 特殊情况下二分队列优化到 O(n) )
\
给定一个长度为
化简得
即
两边同时平方得
再化简得
| 由 |
|---|
套模板即可。
对于二分队列做法,还存在更优的复杂度。
考虑算法本身的复杂度,其瓶颈在于二分判断一个数开始比另一个数优的位置,而在本题中可以通过解不等式解决。
具体来讲,
取
(可以使用这种方法所有斜率优化题,因为可以使用同样的因式分解证明四边形不等式)
例题 2:诗人小 G(半在线)
小 G 是一个出色的诗人,经常作诗自娱自乐。但是,他一直被一件事情所困扰,那就是诗的排版问题。
一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。
小 G 给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。
显然排版时,不应改变原有的句子顺序,并且一个句子只能在一行内。
在满足上面两个条件的情况下,小 G 对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的 P 次方,而一个排版的不协调度为所有行不协调度的总和。 请你对几首首诗进行排版,使得排版后的诗不协调度尽量小,并输出排版的结果。
解析:
不协调度只与每个句子的长度有关,所以设每个句子的长度为
显然设
:::info[四边形不等式证明]
套进第二个定义的式子,得
两边同时消掉
设
考虑函数
图像链接,发现函数下凸。
由于
(可以使用求导或因式分解的方法证它是下凸的)
| 由此还可以得出:如果 |
|---|
套模板即可,注意要求半在线,此外还要注意溢出。
四、四边形不等式(决策单调性)实战 2.0
\ 考虑把状态维数从一维升到二维,但二维 DP 种类较多,需结合题目分析。
以下部分定义
例题 3:Optimal Binary Search Tree(区间 DP)
\
求一棵有
(这题是四边形不等式优化 DP 的起源)
解析:设
这样直接做是
首先考虑证明
:::info[四边形不等式证明]
设
-
若
k_{0} \in [ b,c] -  若
k_{1} \in \left[ a,k_{0} \right] 则 
f_{a,d} + f_{b,c} & = f_{a,k_{0}} + f_{k_{0} + 1,d} + f_{b,k_{1}} + f_{k_{1} + 1,c} + w_{a,d} + w_{b,c} - a_{k_{0}} - a_{k_{1}} \\ & \geq f_{a,k_{0}} + f_{k_{1} + 1,c} + f_{b,k_{1}} + f_{k_{0} + 1,d} + w_{a,c} + w_{b,d} - a_{k_{0}} - a_{k_{1}} \end{aligned}  由于无法直接处理,考虑归纳,则有 
& f_{a,k_{0}} + f_{k_{1} + 1,c} + f_{b,k_{1}} + f_{k_{0} + 1,d} + w_{a,c} + w_{b,d} - a_{k_{0}} - a_{k_{1}} \\ \geq & f_{a,k_{1}} + f_{k_{1} + 1,c} - a_{k_{1}} + f_{b,k_{0}} + f_{k_{0} + 1,d} + w_{a,c} + w_{b,d} - a_{k_{0}} \\ \geq & f_{a,c} + f_{b,d} \end{aligned}  由于当
a = b 或c = d 时四边形不等式成立且其他条件下上述论证总会归纳到一个较小的长度,所以归纳成立。\ 此处推导成立要求w 满足四边形不等式。- 若
k_{1} \in \left[ k_{0} + 1,b \right] ,方法与上面类似。
-  若
-
否则,
| 此处推导成立要求 |
|---|
在这题中,
根据前人的智慧,我们知道
先证明
于是可以写出代码如下:
for(int i=1;i<=n;++i)
p[i][i]=i;
for(int len=2;len<=n;++len)
for(int l=1,r=len;r<=n;++l,++r){
for(int k=p[l][r-1];k<=p[l+1][r];++k)
if(f[l][k]+f[k+1][r]-a[k]<f[l][r])f[l][r]=f[l][k]+f[k+1][r]-a[k],p[l][r]=k;
f[l][r]+=s[r]-s[l-1];
}
显然对每一个区间长度的决策量都只有
可以看出,
满足四边形不等式的矩阵称为蒙日矩阵,可以类似地在
例题 4:Yet Another Minimization Problem(多层一维 DP、类莫队)
\
给定一个长度为
| 所以 |
|---|
可以直接分治,不考虑计算
显然所有的在线做法都不能做到均摊
根据莫队的知识,如果只需要左右移动区间端点是可以快速求解的,考虑将这种思想用在分治做法上。
在分治到
显然在计算
整体时间复杂度仍为
计算
int l0,r0,s[100005];
long long ans;
long long w(int l,int r){
while(l0>l)--l0,ans+=s[a[l0]],++s[a[l0]];
while(r0<r)++r0,ans+=s[a[r0]],++s[a[r0]];//注意跟莫队一样先加后减
while(l0<l)--s[a[l0]],ans-=s[a[l0]],++l0;
while(r0>r)--s[a[r0]],ans-=s[a[r0]],--r0;
return ans;
}
特别地,如果使用半在线分治,需要两个莫队分别求
这样做是因为由于两个部分的约束分别由决策点和状态点给出,如果只用一个莫队会在
例题 5:[IOI2000] 邮局(多层一维 DP)
upd:现在它叫 [IOI2000] 邮局 加强版,因为原题数据范围并没有这么大。
高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。
邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。
你要编写一个程序,已知村庄的位置
解析:
考虑每个邮局控制的区间
设
第一个式子考虑将中点两侧的村庄两两配对,每对村庄的贡献都是后面的减去前面的。
:::info[四边形不等式证明]
之前的那一版不忍直视,重构了一遍。
我们要证的是
-
(y-x+1)\bmod 2=0 这种情况下左式比右式多
x_{{\text{mid}_{x,y}}+1}-x_{\text{mid}_{x,y}} 。例:
y-x+1=6 的情况,贡献系数为:
| 相对位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | |
| 0 | -1 | -1 | -1 | 1 | 1 | 1 | 0 | |
| 左式 | -1 | -2 | -2 | -2 | 2 | 2 | 2 | 1 |
| -1 | -1 | -1 | 0 | 1 | 1 | 1 | 0 | |
| 0 | -1 | -1 | -1 | 0 | 1 | 1 | 1 | |
| 右式 | -1 | -2 | -2 | -1 | 1 | 2 | 2 | 1 |
| 差 | 0 | 0 | 0 | -1 | 1 | 0 | 0 | 0 |
-
(y-x+1)\bmod 2=1 这种情况下左右两式相等。
例:
y-x+1=5 的情况,贡献系数为:
| 相对位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| -1 | -1 | -1 | 0 | 1 | 1 | 1 | |
| 0 | -1 | -1 | 0 | 1 | 1 | 0 | |
| 左式 | -1 | -2 | -2 | 0 | 2 | 2 | 1 |
| -1 | -1 | -1 | 1 | 1 | 1 | 0 | |
| 0 | -1 | -1 | -1 | 1 | 1 | 1 | |
| 右式 | -1 | -2 | -2 | 0 | 2 | 2 | 1 |
| 差 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
:::
可以继续用分治做,但还有更好的方法。
再次根据前人的智慧,可以知道
证明见OI-wiki。
这种方法的代码如下,和区间 dp 十分相似:
for(int j=1;j<=m;++j){
p[n+1][j]=n;
for(int i=n;i;--i){//逆序循环是因为要使用p(i+1,j)
for(int k=p[i][j-1];k<=p[i+1][j];++k)
if(dp[k][j-1]+w(k+1,i)<dp[i][j])dp[i][j]=dp[k][j-1]+w(k+1,i),p[i][j]=k;
}
}
与之前类似地,对于任意
其实还有更快的办法。
例题 6:[IOI2000] 邮局 加强版(wqs 二分优化)
upd:现在它叫 [IOI 2000] 邮局 加强版 加强版,理由同上。
在此题中,
再再次使用前人的智慧,发现
:::info[凸性证明]
要证明凸性,只需证明
同样考虑区间分划,将
将
大概长这样,其中数字表示分隔点的序号,颜色表示原来的划分:
对
| 在其它题目中,以上推导成立要求 |
|---|
知道凸性之后,就可以用 wqs 二分解决了。
为了方便,使用原题目中的样例进行演示,它的图像长这样(由于点
由于斜率具有单调性,考虑二分斜率。
当枚举到一个斜率
图像链接
可以发现,当且仅当一个点作出的直线是最下面的那条(也就是这个点是切点),连接这个点的两条线段的斜率
考虑如何快速计算最下面的那条直线,从截距
可以发现,要计算这个东西只要将价值函数设为
注意答案对应的斜率可能会对应多个段数(即形成平台),比如当
因此,在转移时,要注意只能取到平台的一端(也就是最小答案对应的最小 / 最大划分段数),同时注意最终答案是
为什么取
容易发现它不能输出正确方案,可能可以通过微调的方式避免平台从而输出正确方案,但我也不知道怎么微调。
各种写法的代码如下:
:::info[二分队列写法 1(取平台左侧)]
void solve(){
deque<int> q;
l[0]=1,r[0]=n;
q.push_back(0);
for(int i=1;i<=n;++i){
while(!q.empty()&&i>r[q.front()])q.pop_front();
dp[i]=w(q.front(),i)-Mid,cnt[i]=cnt[q.front()]+1;
while(!q.empty()&&w(q.back(),l[q.back()])>=w(i,l[q.back()]))
q.pop_back();
if(q.empty()){
q.push_back(i);
l[i]=i+1,r[i]=n;
}
else if(w(q.back(),r[q.back()])<=w(i,r[q.back()])){
if(r[q.back()]<n){
q.push_back(i);
l[i]=r[q.back()]+1,r[i]=n;
}
}
else{
int L=l[q.back()],R=r[q.back()],mid,p;
while(R>=L){
mid=(R+L)/2;
if(w(q.back(),mid)>=w(i,mid))p=mid,R=mid-1;
else L=mid+1;
}
r[q.back()]=p-1,l[i]=p,r[i]=n;
q.push_back(i);
}
}
}
//主函数内二分部分
long long ans,L=-1e14,R=0;
while(R>=L){
Mid=(L+R)/2;
solve();
if(cnt[n]<=m)ans=dp[n]+m*Mid,L=Mid+1;//当最大点在n左侧时统计答案
else R=Mid-1;
}
:::
:::info[二分队列写法 2(取平台右侧)]
void solve(){
deque<int> q;
l[0]=1,r[0]=n;
q.push_back(0);
for(int i=1;i<=n;++i){
while(!q.empty()&&i>r[q.front()])q.pop_front();
dp[i]=w(q.front(),i)-Mid,cnt[i]=cnt[q.front()]+1;
while(!q.empty()&&w(q.back(),l[q.back()])>w(i,l[q.back()]))//这里变了
q.pop_back();
if(q.empty()){
q.push_back(i);
l[i]=i+1,r[i]=n;
}
else if(w(q.back(),r[q.back()])<w(i,r[q.back()])){//这里变了
if(r[q.back()]<n){
q.push_back(i);
l[i]=r[q.back()]+1,r[i]=n;
}
}
else{
int L=l[q.back()],R=r[q.back()],mid,p;
while(R>=L){
mid=(R+L)/2;
if(w(q.back(),mid)>w(i,mid))p=mid,R=mid-1;//这里变了
else L=mid+1;
}
r[q.back()]=p-1,l[i]=p,r[i]=n;
q.push_back(i);
}
}
}
//主函数内二分部分
long long ans,L=-1e14,R=0;
while(R>=L){
Mid=(L+R)/2;
solve();
if(cnt[n]<m)L=Mid+1;
else ans=dp[n]+m*Mid,R=Mid-1;//当最大点在n右侧时统计答案
}
:::
:::info[半在线分治的写法(以取平台左侧为例,二分写法相同就不放了)]
void solve(int l,int r){
if(r==l+1)return;
int mid=(l+r)>>1;
p[mid]=p[l],dp[mid]=w(p[mid],mid),cnt[mid]=cnt[p[mid]]+1;
for(int i=p[l]+1;i<=p[r];++i)
if(w(i,mid)<dp[mid]||(w(i,mid)==dp[mid]&&cnt[i]+1<cnt[mid]))dp[mid]=w(i,mid),p[mid]=i,cnt[mid]=cnt[i]+1;
solve(l,mid);
for(int i=l+1;i<=mid;++i)
if(w(i,r)<dp[r]||(w(i,r)==dp[r]&&cnt[i]+1<cnt[r]))dp[r]=w(i,r),p[r]=i,cnt[r]=cnt[i]+1;
solve(mid,r);
}
:::
习题:
当你发现转移式中含有区间最值和一些凸函数的组合时,就可以考虑四边形不等式了。
- 一维 DP
Yakiniku Restaurants\ [HNOI2008] 玩具装箱\ Max (Sum - Max)\ [COCI 2023/2024 #5] Rolete\ 「雅礼集训 2017 Day5」珠宝 /「NAIPC2016」Jewel Thief
-
二维 DP
- 区间 DP
[NOI1995] 石子合并\ [IOI1998] Polygon
- 区间拆分
Ciel and Gondolas\ [CmdOI2019] 任务分配问题\ 忘情
-
所有(?的单调队列 / 斜率优化题目