洛谷P2900 [USACO08MAR]土地徵用Land Acquisition(動態規劃,斜率優化,決策單調性,線性規劃,單調隊列)

洛谷題目傳送門html

用兩種不同的思路立體地理解斜率優化,你值得擁有。函數

題意分析

既然全部的土地都要買,那麼咱們能夠考慮到,若是一塊土地的寬和高(實際上是蒟蒻把長方形立在了平面上)都比另外一塊要小,那麼確定是直接併購,這一塊對答案沒有任何貢獻。優化

咱們先把這些給去掉,具體作法能夠是,按高爲第一關鍵字,寬爲第二關鍵字從大到小排序,而後上雙指針掃一遍。spa

因而,剩下的就是一個高度遞減、寬度遞增的矩形序列。考慮怎樣制定它們的併購方案會最優。顯然若是要併購,必定要挑序列中的一段區間,這樣貢獻答案的就只有最左邊矩形的高乘上最右邊矩形的寬,中間的又是沒有貢獻了。3d

\(f_i\)爲前\(i\)個矩形的最小花費,\(w\)爲寬,\(h\)爲高,直接寫出一個\(O(n^2)\)的方程指針

\[f_i=\min\limits_{j=1}^i\{f_{j-1}+w_ih_j\}\]code

一看貌似是一個決策單調性優化的式子。然而。。。。。。htm

初中生都會的函數圖像法

這種理解方法是在決策單調性優化DP的基礎上應運而生的。blog

或者說,(在大多數狀況下)斜率優化能夠看做決策單調性優化的一種特殊情形。蒟蒻建議仍是先入手決策單調性再來斜率優化吧。排序

蒟蒻的DP各類優化總結

蒟蒻以前寫的一道經典()決策單調性題的題解戳這裏(Lightning Conductor)

對於每個\(f_{j-1}+w_ih_j\),咱們均可以把它視爲一個直線\(l_j:y=ax+b\),其中\(a=h_j,b=f_{j-1}\)。對於每個\(i\),咱們就是須要求出全部\(j\le i\)的直線的\(x\)\(w_i\)時最小的一個\(y\)值。仍然用KmPlot畫一個咱們須要維護的全部直線的樣子,它們應該知足斜率依次遞減。

\(l_1:y=2x;\)

\(l_2:y=x+1;\)

\(l_3:y=\frac x 2+3;\)

\(l_4:y=\frac x 6+5.\)

真正有用的部分

這樣的話,咱們就用單調隊列維護若干個斜率遞減的函數。咱們仍然須要按照決策單調性的作法,維護相鄰兩個決策直線間的臨界值(交點)\(k\)。難道還要維護決策二分棧,對每一個臨界值都二分麼?

這些決策不是直線嗎?求兩個直線的交點。。。。。。初中數學就教了,是\(O(1)\)的。也就是對兩個相鄰決策直線\(l_1,l_2\),咱們求\(\frac{b_2-b_1}{a_1-a_2}\)。其它過程跟決策單調性是如出一轍的。直線入隊前,若是隊尾不知足斜率遞增則出隊。求\(f_i\)以前,先把隊首臨界值\(\le w_i\)的決策出隊,那麼如今隊首就是最優決策了。

這樣求出\(f_n\)只須要\(O(n)\)的時間。

高中生都會的線性規劃法

這纔是理解斜率優化的正宗方法,由於上面並無充分體現對斜率的處理過程。

上面對兩個相鄰直線求\(\frac{b_2-b_1}{a_1-a_2}\),看起來有點像求什麼東西。

咱們原來把決策當成直線,斜率爲\(a\),截距爲\(b\)。如今咱們換一下。把決策\(f_{j-1}+w_ih_j\)看做一個點\(p_j(x,y)\),其中\(x=-h_j,y=f_{j-1}\)

如今要求解的問題又變成了什麼樣子呢?在平面上有若干個點,把\(f_i\)當作目標函數\(z\),咱們須要找到\(f_i=w_ih_j+f_{j-1}\)\(z=-w_ix+y\)的最小值。這不是個線性規劃麼?

把式子變成\(y=w_ix+f_i\),如今就讓咱們來最小化截距\(f_i\)。手(nao)動(dong)模擬一下,咱們如今正在拿着一個斜率爲\(w_i\)的直線,從下往上移動,當第一次通過某個決策點的時候,直線的在\(y\)軸上的截距就是咱們要求的目標函數\(f_i\)的最小值了。

隨便畫一堆點就能夠發現,不管直線以怎樣的斜率向上靠,總有一些點永遠都不會第一次與直線相交,也就是說這些決策是沒用的。剩下的有用的決策點會構成一個凸包:(由於要畫點因此換成了GeoGebra)

凸包的性質就是斜率遞增/遞減。在此題中,由於\(w\)遞增,因此咱們的單調隊列中存的是若干個點知足\(x\)遞增(\(h\)遞減),\(y\)遞增,並且相鄰兩個點的斜率也遞增。這和原序列的順序是同向的。假設隊尾下標爲\(t\),當須要在隊尾加入一個新的決策點時,咱們可能會遇到這樣的狀況:

這時候\(p_t\)已經不優了,咱們把它出隊,如此直到知足斜率遞增爲止,\(p_i\)就能夠入隊了。和上面那種理解方法的寫法差很少,求相鄰兩個點造成的直線斜率而後比一下大小。隊首的處理跟上面那種理解方法的寫法也差很少,若是隊首與後一個的斜率小於\(w_i\)就出隊。最後的隊首依然是最優解。

實現

兩種實現的代碼長得都差很少,都要搞一個單調隊列,都要求臨界值/斜率。因此就放一個代碼吧。。。

複雜度\(O(n\log n)\),瓶頸居然在sort上?!蒟蒻可不想來什麼wys排序

#include<cstdio>
#include<algorithm>
#define RG register
#define R RG int
#define G c=getchar()
#define Calc(i,j) (f[j-1]-f[i-1])/(a[i].h-a[j].h)
//method1:求出臨界值
//method2:求出斜率
using namespace std;
const int N=1e5+9;
int q[N];
double f[N],k[N];
//method1:k_i爲決策直線q_i與q_i+1的臨界值(交點)
//method2:k_i爲決策點q_i與q_i+1所連成直線的斜率
struct Land{
    int w,h;//結構體排序
    inline bool operator<(RG Land&x)const{
        return h>x.h||(h==x.h&&w>x.w);
    }
}a[N];
inline int in(){
    RG char G;
    while(c<'-')G;
    R x=c&15;G;
    while(c>'-')x=x*10+(c&15),G;
    return x;
}
int main(){
    R n=in(),i,h,t;
    for(i=1;i<=n;++i)
        a[i].w=in(),a[i].h=in();
    sort(a+1,a+n+1);
    for(h=i=1;i<=n;++i)//雙指針掃描去除無用矩形
        if(a[h].w<a[i].w)a[++h]=a[i];
    n=h;
    for(h=i=1,t=0;i<=n;++i){
        while(h<t&&k[t-1]>=Calc(q[t],i))--t;//維護臨界值/斜率單調
        k[t]=Calc(q[t],i);q[++t]=i;//加入決策直線/決策點
        while(h<t&&k[h]<=a[i].w)++h;//彈出已經不優的決策
        f[i]=(double)a[q[h]].h*a[i].w+f[q[h]-1];//求出最優解
    }
    printf("%.0lf\n",f[n]);
    return 0;
}
相關文章
相關標籤/搜索