学习笔记——四边形不等式
前言#
模拟赛出四边形不等式部分分了!被我用暴力艹过了这一档部分分!
注意!文中的式子都是用了取 作为转移,如果是 也是一样的。细节会有所区别。
四边形不等式#
定义#
对于一个二元函数 ,
,都有 ,
则称这个二元函数满足四边形不等式。
也就是市面上常说的:包含大于交叉。
基本判定定理#
如果 满足对任意 ,都有 ,则 满足四边形不等式。
挺显然又好证(数归),那证明就略吧。
决策单调性优化动态规划#
四边形不等式不过是一个用来证明决策单调性的工具。而决策单调性才是对 dp 进行优化的关键。
1D1D 单调优化#
四边形不等式可以用来优化对于形式如:
的转移式子,如果其中 是满足四边形不等式的,那么这个 就满足决策单调性。
Proof
我们采用反证法:
假如当前有 ,其中 是 的最优决策点,假如 会是 的最优决策点,那么有:
但是根据四边形不等式,我们有:
将两式相加,消去同类项后得到:
也就是对于 而言, 将会是更优的决策点,这与假设: 是 的最优决策点相矛盾。故 不会是 的最优转移点。
我们利用它的单调性,维护一个决策点数组 。容易发现, 一定是由若干段连续的相同元素构成的。然后我们在更新了 之后,考虑更新它之后的 ,不难想到一定是更新一段后缀,所以我们只要找到这个分界点,后面用 作决策点更优,而前面用原来的 是更优的。这个东西可以二分。
实现的时候,我们用队列中存三元组的方式来代替直接维护整个 。具体地,三元组 表示 中每个位置的当前最优决策点是 ,然后把 数组从前往后这样压缩起来。这样在更新 的时候,从队头找到第一个包含 的(不包含可以直接弹出了),直接 转移。然后用 来更新后面的 数组,就是从队尾一个一个弹出,如果对于当前三元组 ,对 来说决策点 劣于 ,说明整个都是 来转移更优,那就直接弹出;否则,我们在 之间二分找到那个分界点,然后修改队尾的 ,并压入 ,其中 是第一个以 为最优决策点的点(也就是你二分得到的),然后结束。
我的叉点
- 二分注意细节,不要写错;
- 最后得到二分结果 之后,需要判断 是否大于 ,否则会在之后从队尾遇到它然后产生一些错误。
例题
我们有 ,满足四边形不等式,直接用上面说的优化就可以了。
My Code
const int MAXN=1e5+10;
const ll MAX=1e18;
long double pre[MAXN],dp[MAXN],L;
int P;
long double w(int l,int r){
long double ret=abs(pre[r]-pre[l]-L+r-l-1),v=1;
rep(i,1,P) v*=ret;return v;
}
struct RG{int l,r,p;};
string s[MAXN];
int p[MAXN];
void print(int i){
if(p[i]) print(p[i]);
rep(j,p[i]+1,i) cout<<s[j]<<" \n"[j==i];
}
void solve(){
int n;cin>>n>>L>>P;
rep(i,1,n) cin>>s[i],pre[i]=pre[i-1]+s[i].size();
deque<RG> q;q.push_back(RG{1,n,0});
rep(i,1,n){
while(!q.empty()&&(q.front().r<i||q.front().l>q.front().r)) q.pop_front();
p[i]=q.front().p;dp[i]=dp[p[i]]+w(p[i],i);q.front().l=i;
while(!q.empty()&&dp[q.back().p]+w(q.back().p,q.back().l)>=dp[i]+w(i,q.back().l))
q.pop_back();
if(q.empty()){
q.push_back(RG{i,n,i});
continue;
}
int l=q.back().l,r=q.back().r,pp=q.back().p,ans=r+1;
while(l<=r){
int mid=(l+r)>>1;
if(dp[pp]+w(pp,mid)>=dp[i]+w(i,mid))
r=mid-1,ans=mid;
else l=mid+1;
}
q.back().r=ans-1;
if(ans<=n) q.push_back(RG{ans,n,i});
}
if(dp[n]>MAX) cout<<"Too hard to arrange\n";
else cout<<(ll)dp[n]<<'\n',print(n);
cout<<"--------------------\n";
while(!q.empty()) q.pop_back();
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;for(cin>>T;T--;)
solve();
return 0;
}
2D1D 分层转移优化#
这种题一般具有相邻层之间转移的特征。并且同一层转移的时候具有决策单调性。这个单调性,我们同样是看 是否满足四边形不等式。比如说,在我们 1D1D 优化的基础上,规定分成恰好 段,这样就变成了:
此时,我们发现与 1D1D 优化不同的是,我们在求某一层的时候,上一层的答案已经求好了,并且在当前层具有决策单调性。那考虑递归分治来 dp。即令 表示在求 一段的 值时,最优决策点在 一段。然后我们求 ,并暴力枚举找出 的最优决策点。然后根据决策单调性,分治往下做。这样复杂度是 每层,共 层,故复杂度优化为 。
例题
看到这题容易想到枚举每个邮局控制哪段区间,就发现转移式子和上面说的是一样的。好现在我们只需要考虑 是否满足四边形不等式就可以了。
首先, 的含义是, 一段内放置一个邮局,使得 的所有村庄到这个邮局的最小距离和。不难发现,邮局一定是放在中位数的地方——如果是奇数个村庄,就放最中间的村庄处;否则就放最中间两个村庄的中间。
然后你发现这个东西是满足四边形不等式的,可以用上述优化。可惜的是,每次暴力求 复杂度不够优秀。但是容易发现,这个东西可以递推。初始的时候 ,然后 实现转移。
这样转移就是:
递归分治优化即可。
My Code
const int MAXN=3010;
int w[MAXN][MAXN],d[MAXN],V,P;
int dp[MAXN][MAXN];
void DP(int D,int l,int r,int p,int q){
if(l>r) return;
int mid=(l+r)>>1,k=p,mx=dp[D-1][k]+w[k+1][mid];
rep(i,p+1,min(q,mid-1)){
if(dp[D-1][i]+w[i+1][mid]<mx)
mx=dp[D-1][i]+w[i+1][mid],k=i;
}dp[D][mid]=mx;
DP(D,l,mid-1,p,k);DP(D,mid+1,r,k,q);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>V>>P;
rep(i,1,V) cin>>d[i];
rep(len,2,V) for(int l=1;l+len-1<=V;l++){
int r=l+len-1;
w[l][r]=w[l+1][r]+d[l+(r-l+1)/2]-d[l];
}
rep(i,1,V) dp[0][i]=INF;
rep(D,1,P) DP(D,1,V,0,V);
cout<<dp[P][V]<<'\n';
return 0;
}
四边形不等式对区间 dp 的优化#
个人认为这不属于决策单调性的范畴,但是也是四边形不等式的重要运用之一。
区间 dp 的转移通常形如:
我们暂时称之『区间转移式』。
区间包含单调性#
为了对区间 dp 进行优化,我们需要提出这个新的概念。区间包含单调性,即对于一个二元函数 ,对于 ,满足 ,即包含大于被包含,就称这个二元函数具有区间包含单调性。
定理#
如果对于一个区间转移式,满足:
- 满足四边形不等式;
- 具有区间包含单调性;
- 有边界 。
则有 满足四边形不等式。
Proof
采用数学归纳法。
当 时,。
我们采用基本判定定理,要证 满足四边形不等式,我们只要证明 。下面进行分讨:
- 如果对 来说,最优决策点在 。。即:。
- 如果对 来说,最优决策点在 。。即:。
综上,当 时,上述定理成立。
接下来进行归纳。若对于 时,该定理成立,则证明当 时成立:
令 的最优决策点为 , 的最优决策点为 ,必然有 。此时有:
而对于 和 来说,这两个点必然分别是决策点之一,但不一定最优,故有:
然后根据 有四边形不等式以及在 时, 满足四边形不等式,有:
代入上面推出的式子,得到 。
单调性#
我们记 是 的最优决策点。则有结论:
Proof
我们记 ,则 ,有(已经证明 满足四边形不等式):
移项,得到:
又 ,则有:
然后将两式相加,移项,得到:
两边同时加上 ,不难发现这就是对于 的两个决策点的转移式。并且我们发现, 转移时更优的,也就是说,对于所有的 ,都不如用 转移更优,所以有 。
同理可以证明 。
应用#
接下来我们运用这个单调性来优化区间 dp。
不难发现,在跑区间 dp 的时候,对于 , 和 都是已经求出的,我们只要在这个区间内枚举决策点就可以了。
容易证明复杂度为 。
方便地证明四边形不等式#
为了让你能够看出某个二元函数满足四边形不等式,整理如下性质(摘自 oi-wiki):
- 若 均满足四边形不等式(或区间包含单调性),则对于任意 ,函数 也满足四边形不等式(或区间包含单调性)。
- 若存在函数 使得 ,则函数 满足四边形不等式。当函数 单调递增时,函数 还满足区间包含单调性。
- 若 是一个下凸[1]的增函数, 满足四边形不等式和区间包含单调性,则 也满足四边形不等式和区间包含单调性。
- 若 是一个下凸的函数, 满足四边形不等式和区间包含单调性,则 满足四边形不等式。
练习#
一些奇怪的优化 trick 可能会出现在这里。
主要找的是决策单调性优化的题目。
现在有 个人,连续的若干人可以分为一组,你要分成 组,使得每一组的权值和最小。定义一组人的权值为 ,其中 为当前组人的个数。 为给定的 的矩阵。
Solution
感觉这种分组题就大概率是可以用四边形不等式优化的。
首先,我们考虑 是什么。容易发现,就是 中横纵坐标都在 中的所有数的和的一半。这个一半可以最后再说,所以我们直接对 求二维前缀和。这样就有:,显然满足四边形不等式。
然后你发现这题可以直接套用上面邮局的做法,然后你就做完了。
给你一个长度为 的序列,你要把它分成连续 组,使得每一组的权值和最小。一组数的权值定义为这一段中逆序对的个数。
Solution
还是非常显然的转移。然后你考虑逆序对个数是否满足四边形不等式。满足吗?满足啊!然后还是一样套个板子就写完了/qd。然后你还需要用树状数组来预处理区间逆序对的个数。
后记#
能用的题好少啊,大多数都涉及到凸优化啊以及一些 wqs 二分。或者就是能用斜率优化直接艹。
那下次去学学 wqs 二分吧/qd。
指一阶导函数单调递增的函数。 ↩︎
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效