1. 程式人生 > 其它 >【筆記】Slope trick

【筆記】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;
}