斜率DP總結

2022-06-02 10:21:11 字數 4351 閱讀 2537

三個月後第一次寫部落格,我們從這個題開始:

這道題dp方程比較好寫:用dp[i]表示1到i全部被控制的最小代價,那麼dp[i]=min/*表明j+1到i被i守衛*/

然後o(n^2)大t特t。

這裡就要用到斜率優化dp,下面給出我推導這道題的過程。

設i從j轉移比從k轉移要優,那麼:

dp[j]+(i-j)*(i-j-1)/2

dp[j]+[i^2-2ij+j^2-i+j]/2

dp[j]-dp[k]

dp[j]-dp[k]

i(k-j)

ji-->

-/(j-k)>i

j>k:-/(k-j)

-/(j-k)

由此可以得到乙個很像斜率的東西:令yi=i(i+1)/2+dp[i],xi=i,那麼你發現這個式子變成了:

jij>k:(yj-yk)/(xj-xk)

這個東西維護起來要好很多,因此yj,yk,xj,xk都是不受i的影響的,如果你能把式子化成類似這樣的形式,那麼你幾乎已經成功了。

一般斜率dp使用單調佇列進行維護。(下面描述中,我們用g(a,b)表示(ya-yb)/(xa-xb))

隊頭維護:本題中優於i是遞增的,用a表示佇列第一項,用b表示佇列第二項(a

隊尾維護:(這個你也可以畫圖維護乙個類似凸包的東西,但我更喜歡直接推)

用a表示隊尾倒數第二項,用b表示隊尾最後一項,用c表示當前要插入的元素(a

可以發現當g(a,b)>g(b,c)時,b不可能成為最優解。

1、當g(a,b)>i,表明a優於b,b不是最優解。

2、當g(a,b)

因此可以將b彈出。

轉移的時候直接取出隊頭元素進行轉移即可,整個dp複雜度變為o(n),a掉此題。

注意點:上述過程中,請重視正負性的問題,這也許會導致不等式變號,從而改變整個式子。

來個簡單點的題目:其實這道才是我的入門題啊!)

貼**:(注:可以用乘積式代替g函式)

#include using

namespace

std;

typedef

long

long

ll;const

int maxn=1000005

;int

n,front,rear;

ll a[maxn],dp[maxn],q[maxn];

ll y(ll a)

ll x(ll a)

double

g(ll a,ll b)

intmain()

printf(

"%lld\n

",dp[n]);

return0;

}

這題比上一題不同之處在於,它到控制它的控制站之間的牧場數目(不包括自身,但包括控制站所在牧場)乘上該牧場的放養量這句話有點礙眼。

單單使用乙個sum字首和似乎不能表示出dp方程。

如果i控制j+1到i之間的牧場,那麼新的cost就是(i-j-1)*b[j+1]+(i-j-2)*b[j+2]+……+1*b[i-1]+0*b[i]+a[i](a[i]後面忽略不計)

你發現b[j+1]到b[i]的係數剛好都是-1下去的,所以你考慮維護乙個square陣列,square[i]=σb[x]*x|1<=x<=i。

然後square[i]-square[j]=b[i]*i+b[i-1]*(i-1)+……+b[j+1]*(j+1)。可以用(sum[i]-sum[j])*i-(square[i]-square[j])來表示上面那個式子。

那麼dp方程寫出來了,用dp[i]表示1到i的站被控制的代價,那麼dp[i]=min

1、首先要化成(yj-yk)/(xj-xk)

2、隊頭處理:考慮a遞增或是遞減的性質,然後通過g(x,y)與a的關係來判斷是否彈出x。

3、隊尾處理:類似於幾何上凸包的形狀,儘管我個人直接推導更不容易錯,即g(x,y)g(y,z)的形式。

4、dp時直接取出隊頭元素計算即可。

5、千萬注意正負性問題,這是最大的易錯點。(當初之所以搞不懂斜率dp就是因為忽視了正負性的因素)

類似的一題:

再來一題:

貼本題**:

#include using

namespace

std;

typedef

long

long

ll;const

int maxn=1000005

;int

n,front,rear,q[maxn];

ll a[maxn],b[maxn],sum[maxn],square[maxn],dp[maxn];

ll x(ll a)

ll y(ll a)

double

g(ll a,ll b)

intmain()

dp[0]=0

; front=rear=0,q[rear++]=0

;

for (ll i=1;i<=n;i++)

printf(

"%lld\n

",dp[n]);

return0;

}

這個題比較複雜,我們慢慢推。

最暴力的dp:需要三維,分別儲存本次割點,上次割點,分割次數,然後再窮舉上上次割點,進行轉移,複雜度o(n^3k)=t到無邊無際。

然後第一步非常的巧,我們畫個圖來說明吧:

割裂順序有兩種,得到的價值分別如下:

1、先割裂sum1和sum2,再割裂sum2和sum3,價值為sum1*(sum2+sum3)+sum2*sum3

2、先割裂sum2和sum3,再割裂sum1和sum2,價值為sum3*(sum1+sum2)+sum1*sum2

發現了什麼-->兩種割裂方式價值是一樣的-->確定了割裂位置的話,割裂得到價值與割裂順序無關。

這個結論的好處就是我們可以直接從開頭一刀刀割向結尾,dp方程變為:(用dp[i][c]表示序列前i項已經割好,且割了c次的最大價值)

利用字首和優化:dp[i][c]=max

複雜度是o(n^2k)的,仍然t的厲害,我們必須再去掉乙個n,o(nk)才能a掉此題。

利用上述的斜率優化嘗試一下:

dp[j][c-1]+(sum[i]-sum[j])*sum[j]>dp[k][c-1]+(sum[i]-sum[k])*sum[k]

(dp[j][c-1]-sum[j]^2)-(dp[k][c-1]-sum[k]^2)>sum[i]*(sum[k]-sum[j])

令yi=dp[i][c-1]-sum[i]^2,xi=sum[i]

jsum[i]-->(yj-yk)/(xj-xk)

j>k:(yj-yk)/(xk-xj)(yj-yk)/(xj-xk)>-sum[i]

後面隊頭隊尾的維護請自行推導,複雜度可以少乙個n,o(nk)應該能過去。

本題空間限制128mb,如果開乙個100000*200的long long陣列=mle,因此需要滾動陣列。

但是這題我調了很久,下面來好好說一說關於0的問題(本題(xj-xk)可能為0)

到(dp[j][c-1]-sum[j]^2)-(dp[k][c-1]-sum[k]^2)>sum[i]*(sum[k]-sum[j])這一步為止,我們只進行加減法,所以這一步的式子是可靠的。

那麼xj-xk=0,所以要判斷yj-yk是否大於0即可,如果yj-yk>0,那麼無論sum[i]等於幾,j都優於k。

好像用乘積式可以解決問題,但是我用乘積式一直wa,所以

換了一種更好的解決0問題的方法(對於本題而言),由於本題0的存在毫無意義,在輸入時把ai=0的全部刪去,以保證xj-xk不等於0。

貼**:

#include using

namespace

std;

typedef

long

long

ll;const

int maxn=100005

;int

n,k,front,rear,q[maxn];

ll a[maxn],sum[maxn],dp[maxn],dp[maxn];

ll x(ll a)

ll y(ll a)

double

g(ll a,ll b)

intmain()

memset(dp,

0,sizeof

(dp));

for (int c=1;c<=k;c++)

for (int i=1;i<=n;i++) dp[i]=dp[i];

}printf(

"%lld\n

",dp[n]);

return0;

}

下面給一道相對簡單的題目:

斜率優化dp總結

本篇部落格主要講的是斜率優化一些最基礎的定義以及做法,高階版可以見 這篇部落格 在某些情況下,dp 的時間複雜度仍然超出了題目的限制,這時我們就要考慮對其進行優化 斜率優化是對決策進行優化的一種方法 它適用於類似 f i min max a i b j c i d j 的方程 斜率優化由乙個單調佇列...

斜率dp模板

維護凸包。我們假設k sum i 那麼j點此時是比i點要更優,但是同時g j,k g i,j sum i 這說明還有k點會比j點更優,同樣排除j點。排除多餘的點,這便是一種優化!接下來看看如何找最優解。設k g x,y 為止,並將d點加入在該位置中。3,求解時候,從隊頭開始,如果已有元素a b c,...

斜率優化 DP

我們知道,有些dp方程可以轉化成dp i f j x i 的形式,其中f j 中儲存了只與j相關的量。這樣的dp方程我們可以用單調佇列進行優化,從而使得o n 2 的複雜度降到o n 可是並不是所有的方程都可以轉化成上面的形式,舉個例子 dp i dp j x i x j x i x j 如果把右邊...