1. 程式人生 > 其它 >單調佇列優化dp & 斜率優化dp

單調佇列優化dp & 斜率優化dp

Chen_jr's blog

ko no SB da!!!

單調佇列優化dp

對於方程

f[i]=min(f[j]+b[j]+a[i]) i-m<j<i

其中a,b為只與i和j有關的函式

顯然可以轉化為

f[i]=min(f[j]+b[j])+a[i] i-m<j<i

可以考慮用單調佇列優化

以min為例,維護一個單調遞增的佇列,即隊首是最小值,與單純的單調佇列用法相同。

for(int i=1;i<=n;++i){
    while(!q.empty() && q.front()<=i-m)q.pop_front();//彈出過期元素
    int j=q.front();
    f[i]=f[j]+b[j]+a[i];
    while(!q.empty() && f[q.back()]+b[q.back()]>f[i]+b[i])q.pop_back();//維護單調性
    q.push_back(i);
}

一些題目

LuoguP2569股票交易

LuoguP2254瑰麗華爾茲

[USACO13NOV]Pogo-Cow S

斜率優化dp

單調佇列優化dp中的項至於i(當前狀態),或j(決策點)有關,斜率優化dp則一般與 i*j 有關

對於方程

f[i]=min(a[i]+b[j]+c[i]*d[j]) j<i

任取決策點k,j (0<k<j<i)

如果j優於k,則有

a[i]+b[k]+c[i]*d[k]>a[i]+b[j]+c[i]*d[j]

兩邊消去\(a_i\),並移項得

c[i]*(d[k]-d[j])>b[j]-b[k]

假設d單調遞增(題設),即 \(d_k<d_j\)

左右同時除以 \(d_k-d_j\)

c[i]<(b[j]-b[k])/(d[k]-d[j])

由於\(c_i\)單調遞增(題設),如果此時j不如k,以後也一定不如k,那麼就可以把j捨棄,用單調佇列實現

左右同時乘\(-1\)

-c[i]>(b[k]-b[j])/(d[k]-d[j])

\(Y_i=b_i,X_i=d_i\),不等式右側可以看作一個斜率式,令\(slope(i,j)=(Y(i)-Y(j))/(X(i)-X(j))\),則如果要把隊首捨棄,則要滿足

-c[i]>slope(q[head],q[head+1])

一定要清楚slope是如何定義的

另外,為了避免出現分母為零的情況,要滿足\(head<tail\)

考慮維護佇列的單調性

對於相鄰的三項 \(j_1,j_2,j_3\) ,如果斜率單調遞減,則有 $slope(j_1,j_2)>slope(j_2,j_3) $,假設 \(-c_i>slope(j_1,j_2)\),那麼一定有 \(-c_i>slope(j_2,j_3)\),只要有元素出隊那麼佇列會清空(維護了個寂寞),所以要滿足斜率單調遞增(視情況而定)

head=1,tail=1;
for(int i=1;i<=n;++i){
    while(head<tail && -c[i]>slope(q[head],q[head+1]))head++;
    int j=q[head];
    f[i]=a[i]+b[j]+c[i]*d[j];
    while(head<tail && slope(q[tail-1],q[tail])>slope(q[tail],i))tail--;
    q[++tail]=i;
}

還有一種幾何意義上的理解方法,真正的用直線的斜率來證明,佇列維護出來的是一個下凸包
(本蒟蒻並不會)

一些題目

P3628 [APIO2010]特別行動隊

P3195 [HNOI2008]玩具裝箱

P2900 [USACO08MAR]Land Acquisition G