\(\rm{Slope\ trick}\) 並不是一個特別的 \(\rm{algorithm}\),只是一個朴素維護折線的方式
一類題目中要維護一類特殊的分段函數,滿足函數連續,每段都是一次函數,斜率為整數
從一道ABC題目開始
ABC217H
設 \(dp_{i,j}\) 表示經過前 \(i\) 次攻擊后當前處於位置 \(j\) 的最小代價,轉移設 \(t_i-t_{i-1}=dst\)
那么轉移只可能從時間段里面能走到的部分走過來,方程即:
其中 \(G_i(x)\) 表示第 \(i\) 次攻擊站到 \(x\) 時付出的代價
不難發現無論 \(d_i\) 取值如何,\(G_i(x)\) 都是一個斜率單調遞增的函數,即只會是 \(y=-x+b\to y=b\) 或 \(y=b\to y=x+b\) 中之一
轉移方程可以看成將若干個凸殼對位加入,其斜率仍然單調遞增,而加上取 \(\min\) 操作還是個凸殼,區別就在將折線的拐點平移了
不難發現其實我們最后需要的答案一定是在斜率為 \(0\) 的地方得到的,所以用兩個堆分別記錄斜率 \(>0\) 和 \(<0\) 的折線
區間取 \(\min\) 對應將凸包拐點平移 \(\pm dst\),原問題中給一個后綴的斜率加 \(1\),前綴斜率減 \(1\) 是新添加轉移點
最后注意堆中的一個節點表示給斜率變化為 \(1\),若出現一個點和其相鄰的點的斜率差不為 \(1\) 時,要存多個當前點
當然也可以開 \((x,num)\) 來處理
具體實現看代碼,轉移的處理仍需讀者自行思考
struct Heap{
multiset<int> st;
int tag;
inline void insert(int x){return st.insert(x-tag),void();}
inline void erase(int x){st.erase(st.find(x-tag)); return ;}
inline void push(int x){tag+=x; return ;}
inline int least(){return tag+(*st.begin());}
inline int most(){return tag+(*--st.end());}
}sl,sr;
int n,ans,lst;
signed main(){
n=read(); rep(i,1,(n<<2|1)) sl.insert(0),sr.insert(0); rep(i,1,n){
int t=read(),d=read(),x=read(); int dis=t-lst; lst=t;
sl.push(-dis); sr.push(dis);
if(d==1){
if(sl.most()<=x){sr.insert(x);continue;}
sl.insert(x); int tmp=sl.most(); sr.insert(tmp); sl.erase(tmp);
ans+=sr.least()-x;
}else{
if(sr.least()>=x){sl.insert(x);continue;}
sr.insert(x); int tmp=sr.least(); sr.erase(tmp); sl.insert(tmp);
ans+=x-sl.most();
}
} print(ans); return 0;
}
CF713C
本題將絕對值函數的兩部分都需要加入代價統計
每次加入拐點的時候給左邊斜率減一,右邊加一,那么造成兩邊的斜率差為 \(2\),所以需要多往堆里面放一個點
另外不難發現轉移函數是一個前綴 \(\min\) 的形式,那么只需要維護左半邊即可
每次加入一個小於當前堆頂的 \(x\) 會產生附加的代價,將凸殼話出來就能發現這個值是 堆頂和 \(x\) 的差
CF1534G
將曼哈頓距離轉化成切比學夫距離有 \((x+y,x-y)\),此時向右和向上的移動變成走到 \((x+1,y+1)/(x+1,y-1)\)
至此有 \(\Theta(nM)\) 的做法即 \(dp_{i,j}\) 表示更改坐標系之后走到了 \((i,j)\),解決了 \(x+y\le i\) 的布置的最小代價,這時候每次的代價仍然是絕對值的形式
有沒有非常熟悉?就還是 ABC 題的轉移方程,只是這時候兩邊函數全了而已
仍然是雙堆維護即可
APIO2016 煙火表演
\(dp_{x,i}\) 表示在 \(x\) 的子樹里面用 \(i\) 時刻點完的最小代價,這里的時刻是相對時刻
合並子樹的時候還是熟悉的斜率相加,這時候不能再維護兩邊了,因為轉移式子比較繁瑣,需要維護整個凸包
但是拐點的個數是合法的,那么可以使用左偏樹來維護,注意我們並不關注斜率大於 \(1\) 的部分,而能造成 \(k\ge 1\) 的有且只有兒子個數個,那么記得上來先彈掉
最后答案的統計比較巧,先計算 \(f(0)\) 即邊權和,再逐一減掉那些小於 \(0\) 的斜率的貢獻