Loading

【笔记】Slope trick

某神奇的函数合并算法——Slope trick。

Codeforces 原文链接

梗概:

对于那么对于一个函数,我们称之为可Slope ,当且仅当函数图像是一个凸包或一条直线。

不失一般性,我们只考虑下凸包。

显然这个函数可以写作一个分段函数。

但是这样写效率太低,我们换一种描述方式。

首先我们记录最左边的函数的一般式,然后通过变化描述整个函数。我们用一个可重集 \(\mathbf{S}\) 表示存在斜率变化的位置集合。

例如函数 \(y=|x|\) ,我们表示为 \(\{x+y=0,\mathbf{S}=\{0,0\}\}\) 。即我们的初始函数为 \(x+y=0\) ,然后在 \(x=0\) 时,进行两次斜率 \(+1\) 操作。

用可并堆维护 \(\mathbf{S}\) ,可以快速合并两个函数。

CF1534G A New Beginning

首先对于每个土豆,一定到经过该点的斜率为 \(-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;
} 
posted @ 2021-10-04 17:03  7KByte  阅读(226)  评论(0编辑  收藏  举报