算法導論————斜率優化

【例題傳送門: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
php


算法分析:

  斜率優化其實就是一個用來優化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^2spa

  比較(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的時候就能夠永久淘汰j1code

  而後來求斜率方程
  由於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]blog

  斜率函數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]就是答案了get


參考代碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef long long LL;
LL a[51000],s[51000],f[51000];
/*
f[i]=min(f[j]+(sum[i]-sum[j]+i-j-1-L)^2)
f[i]=min(f[j]+(s[i]-s[j]-L)^2)
f[j2]+(s[i]-s[j2]-L)^2<=f[j1]+(s[i]-s[j1]-L)^2
f[j2]+(s[i]-L)^2-2*s[j2]*(s[i]-L)+s[j2]^2<=f[j1]+(s[i]-L)^2-2*s[j1]*(s[i]-L)+s[j1]^2
f[j2]+s[j2]^2-2*s[j2]*(s[i]-L)<=f[j1]+s[j1]^2-2*s[j1]*(s[i]-L)
f[j2]-f[j1]+s[j2]^2-s[j1]^2<=2*s[j2]*(s[i]-L)-2*s[j1]*(s[i]-L)
f[j2]-f[j1]+s[j2]^2-s[j1]^2<=2*s[j2]*(s[i]-L)-2*s[j1]*(s[i]-L)
(f[j2]-f[j1]+s[j2]^2-s[j1]^2)/(s[j2]-s[j1])<=2*(s[i]-L)
*/
double slop(int j1,int j2)
{
    return (f[j2]-f[j1]+s[j2]*s[j2]-s[j1]*s[j1])/(s[j2]-s[j1]);
}
int list[51000];int head,tail;
int main()
{
    LL L;int n;
    scanf("%d%lld",&n,&L);L++;
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    s[1]=a[1]+1;
    for(int i=2;i<=n;i++) s[i]=s[i-1]+a[i]+1;
    head=1;tail=1;list[1]=0;
    for(int i=1;i<=n;i++)
    {
        while(head<tail&&slop(list[head],list[head+1])<=2.0*(s[i]-L)) head++;
        int j=list[head];
        f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L);
        while(head<tail&&slop(list[tail],i)<slop(list[tail-1],list[tail])) tail--;
        list[++tail]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}
相關文章
相關標籤/搜索