【笔记】Slope trick
某神奇的函数合并算法——Slope trick。
梗概:
对于那么对于一个函数,我们称之为可Slope ,当且仅当函数图像是一个凸包或一条直线。
不失一般性,我们只考虑下凸包。
显然这个函数可以写作一个分段函数。
但是这样写效率太低,我们换一种描述方式。
首先我们记录最左边的函数的一般式,然后通过变化描述整个函数。我们用一个可重集 \(\mathbf{S}\) 表示存在斜率变化的位置集合。
例如函数 \(y=|x|\) ,我们表示为 \(\{x+y=0,\mathbf{S}=\{0,0\}\}\) 。即我们的初始函数为 \(x+y=0\) ,然后在 \(x=0\) 时,进行两次斜率 \(+1\) 操作。
用可并堆维护 \(\mathbf{S}\) ,可以快速合并两个函数。
首先对于每个土豆,一定到经过该点的斜率为 \(-1\) 的直线上的点最优。
因为改变 \(x\) 或 \(y\) 左边不会使答案更优,必要性,同时必定经过 \(x+y=c\) 的直线,充分性。
所以我们定义状态 \(f[x][y]\) 表示走到 \((x,y)\) ,并覆盖所有 \(a+b\le x+y\) 的所有点的最优代价。
发现 \(x\) 和 \(y\) 作为阶段并不方便,我们设 \((x+y,x)\) 作为阶段,那么有 \(f[i][j]=\min\{f[i-1][j],f[i-1][j-1]\}+val_{i+j}\) 。
\(val\) 函数只与 \(i+j\) 有关,所以我们可以写作 \(f[i][j]-val_{i+j}=\min\{f[i-1][j],f[i-1][j-1]\}\) 。
右边就是邻项取最小值,相当于将单峰函数的一半平移。
所以直接 Slope trick ,两个堆分别维护极值点的左右两边,每次加的都是一个绝对值函数,相当于向集合中加入两个点,极值点最多只会偏移一个单位,所以直接维护复杂度是正确的,时间复杂度 \(\mathcal{O}(N\log N)\) 。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 800005
using namespace std;
int n;
struct node{
int x,y;
bool operator<(const node o)const{return x+y<o.x+o.y;}
}u[N];
priority_queue<int>p,q;
int main(){
scanf("%d",&n);
rep(i,1,n)scanf("%d%d",&u[i].x,&u[i].y);
sort(u+1,u+n+1);
rep(i,1,n+5)p.push(0),q.push(0);
long long ans=0;int cur=0;
rep(i,1,n){
//cout<<"ss "<<u[i].x<<" "<<u[i].y<<" "<<ans<<endl;
cur=u[i].x+u[i].y;
if(p.top()>u[i].x){
ans+=p.top()-u[i].x;
q.push(cur-p.top());p.pop();
p.push(u[i].x);p.push(u[i].x);
}
else if(cur-q.top()<u[i].x){
ans+=u[i].x-(cur-q.top());
p.push(cur-q.top());q.pop();
q.push(cur-u[i].x);q.push(cur-u[i].x);
}
else p.push(u[i].x),q.push(cur-u[i].x);
}
printf("%lld\n",ans);
return 0;
}