標籤:意義 需要 style 證明 bzoj1010 座標 部分 存在 target
【例題傳送門:BZOJ1010】BZOJ1010: [HNOI2008]玩具裝箱toy
【題意】
給出n條連續線段,每條線段都有長度為x[i],我們可以把連續若干條線段連在一起,變成一個組合,兩條線段如果相連,就要在兩條線段中間添加一個長度為1的格子(如果沒有相連就不用添加),假如我們現在選擇把第i條到第j條線段之間的所有線段變成一組合的話,這個組合的總長度就為:x[i]+x[i+1]+x[i+2]+x[i+3]+...+x[j]+j-i,現在給出一個常數L,假設當前選擇的組合的長度為s,那麼這個組合就為我們產生了(s-L)^2的費用,求出把n條線段分成若干組合所需要的最小費用,單獨的線段可以成為一個組合
【輸入檔案】
第一行兩個整數,分別為N和L。
下來N個數字xi,按編號從小到大輸入每個物品的容量。
1<=N<=50000,1<=L,xi<=10^7
【輸出檔案】
一個整數,總花費的最小值。
【範例輸入】
5 4
3 4 2 1 4
【範例輸出】
1
演算法分析:
斜率最佳化其實就是一個用來最佳化DP的演算法,但必須當DP方程具有單調性的時候才能使用
下來我們用例題來說明斜率最佳化
f[i]表示1~i的最小花費。
f[i]=min(f[j]+(sum[i]-sum[j]+i-(j+1)-L)^2) (j<i)
f[i]=min(f[j]+(sum[i]+i-sum[j]-j-1-L)^2) (j<i)
令s[i]=sum[i]+i,L=1+L
則f[i]=min(f[j]+(s[i]-s[j]-L)^2)
首先我們先來證明決策單調性
假設j1<j2<i,在狀態i處的j2決策不比j1決策差(心裡想著淘汰j1),
即要滿足:f[j2]+(s[i]-s[j2]-L)^2<f[j1]+(s[i]-s[j1]-L)^2
則對於i後的所有狀態t,是否j2也不比就j1差?(術語:證明決策單調性)
即f[j2]+(s[t]-s[j2]-L)^2 < f[j1]+(s[t]-s[j1]-L)^2
容易理解s[t]=s[i]+v
所以得到(1)不等式:f[j2]+(s[i]-s[j2]-L+v)^2<f[j1]+(s[i]-s[j1]-L+v)^2
因為已知(2)不等式:f[j2]+(s[i]-s[j2]-L)^2<f[j1]+(s[i]-s[j1]-L)^2
所以化簡(1)不等式:把s[i]-s[j2]-L看成一個整體,v看成一個整體,得到:
f[j2]+(s[i]-s[j2]-L)^2+2*v*(s[i]-s[j2]-L)+v^2 <f[j1]+(s[i]-s[j1]-L)^2+2*v*(s[i]-s[j1]-L)+v^2
比較(2)不等式:
左邊多了一部分:2*v*(s[i]-s[j2]-L)+v^2
右邊多了一部分:2*v*(s[i]-s[j1]-L)+v^2
所以我們只需要證:
2*v*(s[i]-s[j2]-L)+v^2<=2*v*(s[i]-s[j1]-L)+v^2
即:(s[i]-s[j2]-L)<=(s[i]-s[j1]-L)
即: -s[j2] <= -s[j1]
即:s[j1]<s[j2]這是肯定的,所以得證。
總結:對於當前i:j2比j1好,那麼對於t(i<t)來說一樣:j2一樣比j1好,
所以當前i選擇j2,淘汰j1,以後的t也不會在j2存在的時候選擇j1
所以i的時候就可以永久淘汰j1
然後來求斜率方程
因為f[j2]+(s[i]-s[j2]-L)^2<=f[j1]+(s[i]-s[j1]-L)^2
展開:
f[j2]+(s[i]-L)^2-2*(s[i]-L)*s[j2]+s[j2]^2<=f[j1]+(s[i]-L)^2-2*(s[i]-L)*s[j1]+s[j1]^2
即f[j2]-2*(s[i]-L)*s[j2]+s[j2]^2<=f[j1]-2*(s[i]-L)*s[j1]+s[j1]^2
即f[j2]+s[j2]^2-2*(s[i]-L)*s[j2]<=f[j1]+s[j1]^2-2*(s[i]-L)*s[j1]
即[(f[j2]+s[j2]^2)-(f[j1]+s[j1]^2)]<=2*(s[i]-L)*s[j2]-2*(s[i]-L)*s[j1]
即[(f[j2]+s[j2]^2)-(f[j1]+s[j1]^2)]/(s[j2]-s[j1])<=2*(s[i]-L)
對於j來說:
製造的點座標
Y=f[j]+s[j]^2
X=s[j]
我們用隊列list在存有意義的決策點,list中相鄰兩點的斜率遞增(隊列中的點形成一個下凸殼),而且都大於2*(s[i]-L),那麼隊列頭對於i來說就是最優決策點
加入決策i時,令隊尾為list[tail],前一個為list[tail-1]
斜率函數slop(點1,點2)
滿足:slop(list[tail-1],list[tail])>slop(list[tail],i)時,
那麼隊尾list[tail]在三者(list[tail-1],list[tail],i)對於未來的tail絕對不會是最優的策略,所以將其彈出tail--
最後遇到了:slop(list[tail-1],list[tail])<slop(list[tail],i),保證了隊列的相鄰兩點的斜率遞增所以加入i:list[++tail]=i;
然後f[n]就是答案了
參考代碼:
#include<cstdio>#include<cstring>using namespace std;long long f[51000],q[51000],s[51000];int n,L;double X(int i){ return 2.0*s[i];}double Y(int i){ return f[i]+(s[i]+L)*(s[i]+L);}double slop(int i,int j){ return (Y(j)-Y(i))/(X(j)-X(i));}int main(){ scanf("%d%d",&n,&L);L++; s[0]=0; for(int i=1;i<=n;i++) { int x; scanf("%d",&x);x++; s[i]=s[i-1]+x; } int l=1,r=1;q[1]=0; for(int i=1;i<=n;i++) { while(l<r&&slop(q[l],q[l+1])<=s[i]) l++; int j=q[l]; f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L); while(l<r&&slop(q[r-1],q[r])>slop(q[r],i)) r--; q[++r]=i; } printf("%lld\n",f[n]); return 0;}
演算法導論————斜率最佳化