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