[bzoj1010][HNOI2008]玩具裝箱toy_斜率優化dp
玩具裝箱toy bzoj-1010 HNOI-2008
題目大意:P教授要去看奧運,但是他舍不下他的玩具,於是他決定把所有的玩具運到北京。他使用自己的壓縮器進行壓縮,其可以將任意物品變成一堆,再放到一種特殊的一維容器中。P教授有編號為1...N的N件玩具,第i件玩具經過壓縮後變成一維長度為Ci.為了方便整理,P教授要求在一個一維容器中的玩具編號是連續的。同時如果一個一維容器中有多個玩具,那麽兩件玩具之間要加入一個單位長度的填充物,形式地說如果將第i件玩具到第j個玩具放到一個容器中,那麽容器的長度將為 x=j-i+Sigma(Ck) i<=K<=j 制作容器的費用與容器的長度有關,根據教授研究,如果容器長度為x,其制作費用為(X-L)^2.其中L是一個常量。P教授不關心容器的數目,他可以制作出任意長度的容器,甚至超過L。但他希望費用最小。
註釋:$1\le n\le 5\cdot 10^4$,$1\le L,c_i\le 10^7$。
想法:顯然dp。狀態特別簡單,轉移也特別簡單。
狀態:dp[i]表示打包完前i個的最小代價。
轉移:$dp[i]=min(dp[j]+(sum[i]-sum[j]+i-j)^2),1\le j \le i-1$。
然後,過不去是顯然的。我們在此介紹一種優化dp的算法:斜率優化。
就是說,我將一個1D1D型的dp狀態轉移問題可以將一個裝一狀態變成一條直線,其中y=kx+b,b是當前需要轉移的狀態,欽定k是一個常數(這裏,一切除了和dp[i]有關的且和i有關變量均可視為常數)。我們先將從1到i-1所有的點,從通過您列的直線解析式中的y和x的計算法則算一下,就可以得到i-1個點,把這i-1個點扔進坐標系。然後,我逐一欽定這i-1個點,將一條斜率為k的直線經過被欽定點。這樣就會得到一個截距,有題目可知就能知道是最大截距還是最小截距。然後,我們以最大截距為例,首先先保證所有的轉移方程在任何情況下k的正負都是確定的。緊接著我們得出了直接轉移到dp[i]的點j(由於是取最小值,這裏找到了一個滿足最小值的方案),顯然,之前不滿足題意的之後一定也不滿足題意,所以我們就可以把它刪了... ...,這些類似的操作都可以用... ...單調隊列來實現。具體地,大可直接看代碼... ...
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N=50100; ll s[N],Q[N],f[N],n,x,head,L,tail,j; inline double X(ll i)//返回單點橫坐標 { return s[i]; } inline double Y(ll i)//返回單點縱坐標 { return f[i]+(s[i]+L-1)*(s[i]+L-1); } inline double Rate(ll i,ll k)//返回兩點斜率 { return (Y(k)-Y(i))/(X(k)-X(i)); } int main() { scanf("%lld%lld",&n,&L); L++; head=tail=1; for(int i=1;i<=n;i++) { scanf("%lld",&s[i]); s[i]+=s[i-1]; } for(int i=1;i<=n;i++) { s[i]+=i; } for(int i=1;i<=n;i++) { while(!(head>=tail)&&Rate(Q[head],Q[head+1])<2*s[i])head++;//維護單調隊列 j=Q[head];f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L); while (!(head>=tail)&&Rate(Q[tail-1],Q[tail])>Rate(Q[tail],i)) tail--;Q[++tail]=i;//維護單調隊列 } printf("%lld\n",f[n]); return 0; }
由於有了不小心磕々絆々の全球流(手白錯誤合計),以後所有的博客就不寫錯誤了
小結:斜率優化的作用在於優化dp qwq
給各位推薦一篇blog,寫的超級詳細。http://www.cnblogs.com/Paul-Guderian/p/7259491.html
[bzoj1010][HNOI2008]玩具裝箱toy_斜率優化dp