單調佇列優化dp & 斜率優化dp
阿新 • • 發佈:2022-05-13
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); }
一些題目
斜率優化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;
}
還有一種幾何意義上的理解方法,真正的用直線的斜率來證明,佇列維護出來的是一個下凸包(本蒟蒻並不會)
一些題目