[斜率優化dp] HDU 3507 Print Article
阿新 • • 發佈:2020-07-28
題目大意
給你一個序列 \(\{c_n\}\),以及一個正整數 \(M\),現在要將這個序列分割成連續的若干段,每一段的價值是 \(\left(\sum_{i=1}^{k}c_i\right)^2+M\),求最小价值。\((n\leq 500000,M\leq 1000)\)
題解
令 \(Sum[n]=\sum_{i=1}^{n}c_i\),
設 \(dp[i]\) 表示把前 \(i\) 個數分割成若干段的最小价值,那麼有 \(dp[i]=\min\{dp[j]+\left(Sum[i]-Sum[j]\right)^2+M\},j<i\)。
整理後得 \(dp[j]+Sum[j]^2=2Sum[i]Sum[j]+dp[i]-Sum[i]^2-M\)
發現 \(Sum[i]\) 單增,可以斜率優化。
令 \(y=dp[j]+Sum[j]^2,k=2Sum[i],x=Sum[j]\),
原式轉化成了 \(y=kx+dp[i]-Sum[i]^2-M\) ,因為要使dp值最小,所以相當於要使這條直線在 \(y\) 軸上的截距最小,使用單調佇列維護一個下凸殼進行狀態轉移即可。時間複雜度 \(O(n)\)。
Code
#include <iostream> #include <algorithm> #include <cstring> #include <string> #include <cstdio> #include <vector> using namespace std; #define RG register int #define LL long long template<typename elemType> inline void Read(elemType &T){ elemType X=0,w=0; char ch=0; while(!isdigit(ch)) {w|=ch=='-';ch=getchar();} while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar(); T=(w?-X:X); } LL dp[500005],Sum[500005]; int Q[500005]; int N,head,tail;LL M; inline LL x(int i){return Sum[i];} inline LL y(int i){return dp[i]+Sum[i]*Sum[i];} inline LL k(int i){return Sum[i]<<1;} inline void maintain(int i,int j){ dp[i]=dp[j]+(Sum[i]-Sum[j])*(Sum[i]-Sum[j])+M; } LL Solve(){ head=1;tail=0; Q[++tail]=0; for(RG i=1;i<=N;++i){ while(tail-head+1>=2 && y(Q[head+1])-y(Q[head])<=k(i)*(x(Q[head+1])-x(Q[head]))) ++head; maintain(i,Q[head]); while(tail-head+1>=2 && (y(Q[tail])-y(Q[tail-1]))*(x(i)-x(Q[tail-1])) >=(y(i)-y(Q[tail-1]))*(x(Q[tail])-x(Q[tail-1]))) --tail; Q[++tail]=i; } return dp[N]; } int main(){ while(~scanf("%d%lld",&N,&M)){ memset(dp,0x3f,sizeof(dp)); dp[0]=0; for(RG i=1;i<=N;++i){ Read(Sum[i]); Sum[i]+=Sum[i-1]; } printf("%lld\n",Solve()); } return 0; }