我的第一道斜率優化dp。
首先,O(n^2)複雜度的dp是很容易想到的。現在我們需要用斜率優化把每次轉移的複雜度優化到O(1)。考慮從dp(a)和dp(b)轉移到dp(i),若從dp(a)轉移要優,則有dp(a)+(sum(i)-sum(a))^2+M<dp(b)+(sum(i)-sum(b))^2+M,移項得到(dp(a)+sum(a)^2-dp(b)-sum(b)^2)/(2*(sum(a)-sum(b)))<sum(i)。設Y(x)=dp(x)+sum(x)^2,X(x)=2*sum(x),代入後得到(Y(a)-Y(b))/(X(a)-X(b))<sum(i),這不就是斜率嗎!
於是可以得到這樣的關係:(Y(a)-Y(b))/(X(a)-X(b))<sum(i) <=> 由a轉移較優;(Y(a)-Y(b))/(X(a)-X(b))>sum(i) <=> 由b轉移較優。假設有從左到右的a,b,c三個點,如果ab的斜率大於bc的斜率,那麼dp(i)一定不會由b轉移得到。用一個單調隊列去維護遞增的斜率(就像凸包一樣),就可以迅速完成狀態轉移。
當狀態轉移的優劣可以用一個形如“斜率”的式子比較的時候,就可以考慮斜率優化了!另外要注意,能不用除法和浮點數的時候,就不要去用。
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <string>
#include <string.h>
#include <memory.h>
#include <vector>
#include <queue>
#include <stack>
#include <set>
using namespace std;
#define ll long long
int n,m;
ll c[500010];
ll sum[500010];
ll dp[500010];
const ll INF = 1e18;
inline ll getY(int id){
return sum[id]*sum[id] + dp[id];
}
inline ll getX(int id){
return 2*sum[id];
}
int que[500010];
int main(){
while(cin>>n>>m){
for(int i=1;i<=n;i++){
scanf("%I64d",&c[i]);
sum[i] = sum[i-1] + c[i];
}
int head = 0;
int tail = 0;
que[tail++] = 0;
for(int i=1;i<=n;i++){
while(head+1<tail && (getY(que[head+1])-getY(que[head])) <= sum[i] * (getX(que[head+1])-getX(que[head])) ){
head++;
}
int j = que[head];
dp[i] = (sum[i]-sum[j])*(sum[i]-sum[j])+m+dp[j];
while(head+1<tail && (getY(que[tail-1])-getY(que[tail-2])) * (getX(i)-getX(que[tail-1])) >=
(getY(i)-getY(que[tail-1])) * (getX(que[tail-1])-getX(que[tail-2])) ){
tail--;
}
que[tail++] = i;
}
// for(int i=1;i<=n;i++){
// dp[i] = INF;
// for(int j=0;j<i;j++){
// dp[i] = min(dp[i],(sum[i]-sum[j])*(sum[i]-sum[j])+m+dp[j]);
// }
// }
cout<<dp[n]<<endl;
}
return 0;
}