1. 程式人生 > 其它 >「學習筆記」Slope trick

「學習筆記」Slope trick

Slope trick

\(\rm{Slope\ trick}\) 並不是一個特別的 \(\rm{algorithm}\),只是一個樸素維護折線的方式

一類題目中要維護一類特殊的分段函式,滿足函式連續,每段都是一次函式,斜率為整數

從一道ABC題目開始

ABC127H

\(dp_{i,j}\) 表示經過前 \(i\) 次攻擊後當前處於位置 \(j\) 的最小代價,轉移設 \(t_i-t_{i-1}=dst\)

那麼轉移只可能從時間段裡面能走到的部分走過來,方程即:

\[dp_{i,j}=\min\limits_{t\in [j-dst,j+dst]}\{dp_{i-1,t}\}+G_i(j) \]

其中 \(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\) 的斜率的貢獻